پاتونیک - بررسی Underscore در پایتون

سلام به همه دوست‌داران پایتون!
توی این قسمت از مجموعه‌ی پایتونیک قصد داریم در رابطه با Underscore/Underline صحبت کنیم. همون‌طور که (ممکنه) بدونین توی پایتون Underscore معانی مختلفی می‌تونه داشته باشه. بعضی جاها می‌بینیم دوتا از اینا پشت سرهم اومده (myfunc__)، بعضی جاهای دیگه می‌بینم یدونه داریم و خیلی دقیق‌تر بخوام بگم در ادامه پنج‌ حالت مختلف اونارو با هم قراره بررسی کنیم:

  • حالت اول - وقتی که یک Underscore به تنهایی داریم.
  • حالت دوم - وقتی که Underscore قبل از اسم یک متد یا متغییر به کار میره.
  • حالت سوم - وقتی که Underscore بعد از اسم یک متد یا متغییر به کار میره.
  • حالت چهارم - وقتی که دوتا Underscore قبل از اسم یک متد یا متغییر به کار میره.
  • حالت پنجم - وقتی که دوتا Underscore قبل و دوتا هم بعد از اسم یک متد یا متغییر به کار میره.

حالت اول

امکانش هست توی سورس یک برنامه یا توی یک ویديوی آموزشی قطعه کدهایی شبیه پایین دیده باشید:

توی هر دو حالت فوق، در حقیقت برنامه نویس داره به این اشاره می‌کنه که این بخش از اسم متغییر برای ما مهم نیست. مثلاً توی مثال اول، یک حلقه داریم که اسم شمارنده برای ما مهم نیست و هدف انجام دادن اون کار (do_something) هست(صرفا دنبال یک اسم موقت هستیم).
توی مثال دوم، بخش های مختلف یک tuple روی به متغییرهای متناسب با خودش انتصاب می‌کنیم(به این کار unpack کردن گفته میشه) یعنی گفتیم مقدار اول توی تاپل رو بریز توی topic، دومی رو بریز توی dateو سومی ولش کن بریز توی _ چون اصلا برام مهم نیست.. از قضا پارامتر سوم این tuple برای ما مهم نبوده و در ادامه کار به کار نمی‌اومده، بجای فکر کردن به یک اسم مناسب به راحتی با Underscore مشکلمون حل شده.



یه چیز دیگه هم که باید بگم اینِ که اگر با محیط تعاملی پایتون در حال کار باشید، مفسر پایتون آخرین مقداری که نوشته شده رو می‌ریزه توی _، واقعا نشون دادنش راحت تر از گفتنشه، با هم ببینیم:

پس هر کجا دیدیم از underscore به تنهایی برای اسم یک متغییر استفاده شده، سریع به این نتیجه می‌رسیم که این متغییر برای برنامه‌نویس اهمیتی نداشته.

حالت دوم

گفتیم توی حالت دوم، ما قبل از اسم متغییر یا متد یا فانکشن یک underscore بکار می‌بریم. اما خب چه معنی یا چه فایده‌ای داره؟

پایتون مثل زبان‌های برنامه نویسی دیگه خیلی تفاوت مشخصی بین متغییر‌های عمومی و خصوصی قائل نیست. به همین خاطر معمولاً از یک Underscore قبل از اسم متغییر یا متد به کار گرفته میشه تا به شخص دیگه‌ای که داره کد رو می‌خونه یک هینت (Hint) داده بشه که این متغییر یا متد فقط قرار هست فقط در این‌جا به گرفته بشه و قرار نیست به صورت عمومی در همه جا استفاده بشه.

آیا این کار باعث میشه وقتی کلاسمون رو جایی import کردیم نتونیم از اون متغییر یا متد استفاده کنیم؟ خیر!

یک مثال با هم ببینیم، فرض کنیم توی فایل first.py ما رفتیم دو تا متغییر تعریف کردیم و اسم اولین متغییر با _ شروع شده اما دومی کاملا نرمال تعریق شده. خب حالا بریم توی یک برنامه دیگه سعی کنیم از این متغییر‌ها استفاده کنیم ببینیم چه فرقی دارن این دوتا با هم.

خب من الان یک شل تعاملی باز می‌کنم و اولین چیزی که چک میکنم اینه:

همون‌طور که میبینید گویا به راحتی میشه به هر دو متغییر دسترسی داشت! بذارید یه جور دیگه چک کنیم:

اینار اول importاش کردم و بعد سعی کردم بهش دسترسی داشته باشم که در نگاه اول چیزی رو نشون نمیده. اما اگه سعی کنم یه جورایی بزور بهش دسترسی داشته باشم مثل اینکه میشه و هیچ خطای دسترسی هم نمیده:

یه چیز دیگه هم مونده بذارید اونم چک کنیم:

اما خب چرا نشد؟ چون مثل اینکه توی پایتون وقتی اینجوری چیزی رو import می‌کنید گویا هر کلاس، تابع و یا متغییر که اسمش با _ شروع شده باشه به صورت پیش فرض import نمیشه مگر اینکه شما برید بهش بگید این کار رو بکن (__all__).

حالت سوم

توی هر زبان برنامه نویسی یک‌سری کلمات کلیدی داریم که توی اون زبان از قبل رزرو شدن و پایتون هم از این قضیا مستثنی نیست و استفاده از اون اسم‌ها برنامه ما رو شکننده میکنه. اما خب شاید اون اسم واقعا بهترین اسم برای متغییر ما باشه، در نتیجه کافیه برای جلوگیری از این اتفاقات اینجوری عمل کنیم:

حالت چهارم

حالت چهارم وقتیه که دوتا _ قبل اسم یک متغییر استفاده میشه. در حقیقت توی شی‌گرایی خیلی ممکنه subclassهایی داشته باشید که از اسم‌هایی مشابه کلاس پدر استفاده کرده باشن. در نتیجه نیاز به مکانیسمی داریم که جلوگیری کنه از مشکلاتی که ممکنه پیش بیاد به این کار name mangling گفته میشه. name mangling میاد خودش اسم متغییر‌ها رو به طریقی تغییر میده که امکان تصادم خیلی خیلی کمتر بشه.

با مثال دنیا قشنگ‌تر میشه:

اما خب بریم ببینیم چی شده:

خب گویا به دو متغییر(attribute) اول به راحتی دسترسی داریم اما سومی جالبه میگه اصلا همچین ویژگی وجود نداره! بریم یکم بیشتر بررسیش کنیم ببینیم اوضاع از چه قراره:

اینجا داریم تمام ویژگی‌هایی که bahram داره رو میبینیم جالبیه کار اولین ایتم و دو آیتم اخر این لیست برای ما هست. همون‌طور که ابتدای لیست میبینید گویا یک ویژگی جدید داریم که اسم هست Person_full_name__ . خود پایتون اسم اون ویژگی ما رو به نحوی تغییر داده که توی زیرکلاس مساله پیش نیاد. یه چیزی اما همیشه یادتون باشه name mangling وقتی قشنگه که از دید برنامه نویش fully transparent باشه یعنی من توی کدم داخل کلاس با همون اسمی که اول گذاشتم کار کنم (full_name__) خود پایتون بقیه جا بره هر کار دوس داره بکنه.

فرض کنیم ما قصد داریم یک شی جدید به سیستمون اضافه کنیم که مثلا اون شی هم شخصه و یک دانشجو در نتیجه فقط کلاس قبلیمون رو گسترش می‌دیم:

همون‌طور که میبینید دوباره همه‌ی اون متغییرها با همون اسم وجود داره. درنتیجه دوباره انتظار داریم همون داستان قبلی باشه:

اما اصل داستان اینجاس:

اینکه الان فقط به یک دیدِ نسبی برسید که مثلا وقتی دوتا _ بذاریم پایتون داره خودش یه کاری انجام میده به اسم name mangling فعلاً به نظر من کافیه، مگه اینکه واقعا کارهای‌ شی‌گرایی و ... انجام بدید و اون موقع این بخش از کار کاملا واستون روشن میشه.

نکته: راجعبه تلفظ این اسم‌ها full_name__ بین توسعه‌دهندگان پایتون این تلفظ جا افتاده که دوتا _ را میخونن داندِر چون (Double underscore = dunder) یعنی کلا می‌گیم «داندر فول نیم». همین‌طور اگه با چیزی مثل __init__ مواجه شدیم این رو هم می خونیم «داندِر اینیت» لازم نیست بگید «داندِر اینیت داندِر» برای راحتیه کار فقط بخش اول تلفظ میشه.

حالت پنجم

حالت پنجم، گفتیم دوتا _ قبل و بعد از اسم متد یا متغییر باشه. توی این حالت دیگه ربطی به name mangling نداره و پایتون هیچ بلایی سر این متد یا متغییر‌ها نمیاره. ممکنه چیزهایی مثل __init__ (که توی حالت چهارم خودمون هم داشتیم) دیده باشید. اساسا توی خیلی از بخش برای اسم گذاری متد‌های خاصی از این الگو پیروی میشه ( به این جور متد‌ها magic method هم گفته میشه که اینجا میتونید بیشتر بخونید راجعبشون لینک). یه‌جورایی میشه گفت انگار خودمون داریم یک چیزی رو برای یک کار خاصی رزرو می‌کنیم. مثلاً وقتی توی یک کلاس، متد __init__ رو تعریف می‌کنیم داریم تابع سازنده رو مشخص می‌کنیم. اینجوری پایتون مطمئن میشه که این متد‌های خاص منظوره(متد‌های جادویی) هیچ وقت اسمشون با اسم‌ِ متد‌های کاربر تصادم پیدا نمی‌کنه.

جمع‌بندی

توصیه‌ من به شما جوانان پایتون دوست، اینه که اگر دنبال استفاده مفاهیم مثل عمومی یا خصوصی بودن یک متغییر توی پایتون هستید، از یک _ استفاده کنید. دنبال دوتا نباشید مگر اینکه واقعاً مربوط به name mangling باشه.چرا این رو می‌گم چون باعث افزایش خوانایی کدتون میشه. وقتی نفر دیگه‌ای داره کد رو می‌خونه و با کد‌استایل پایتون آشنا باشه سریع میفهمه که آها این تابع، کلاس، متد و یا متد قرار فقط اینجا درون این کلاس به کار گرفته شه.

بیشترِ مطالب اینجا گفته شد و من برای مرور بیشتر لینک‌های زیر رو می‌ذارم

  • لینک‌ِ مراجع استفاده شده در این قسمت:

https://www.pythonmania.net/en/2017/03/05/underscore-in-python/

https://dbader.org/blog/meaning-of-underscores-in-python

https://shahriar.svbtle.com/underscores-in-python

https://hackernoon.com/understanding-the-underscore-of-python-309d1a029edc