یک‌بار برای همیشه - Property

What is property in Python?مقدمهسلام به همه دوست‌داران پایتون!امروز هدف‌مون این هست که یک موضوع خیلی ساده اما کاربردی رو با هم بررسی کنیم. یک مفهومی توی پایتون وجود داره که خیلی مشابه با زبون‌‌های برنامه نویسی دیگه هست. اما خب مثل همیشه انتظار داریم یکم باحال‌تر هم باشه.اول از همه، باید یادآوری کنیم که پایتون برخلاف زبان‌های برنامه‌نویسی دیگه به ما اجازه دسترسی مستقیم به attribute ها رو میده. دسترسی مستقیم به attributeها در پایتونمگه حالت دیگه‌ای هم هست؟ بله! حالتی که شما برای دسترسی به ویژگی‌/attributeها نیاز به یک واسط داشته باشید که عموماً این واسط‌ها تحت نام Setter/Getter شناخته می‌شن، چیزی شبیه قطعه کد پایین:دسترسی غیر مستقیم به attributeها در پایتونخب، بنظر می‌رسه که پایتون با این ترفند، کار ما رو بسیار راحت کرده. اما!اما چی میشه اگر ما واقعاً به این واسط‌ها نیاز داشته باشیم؟یا شاید بهتر باشه اول بگیم که چرا ممکنِ به این‌ واسط‌ها اصلا نیاز داشته باشیم؟حالت اول، تصور کنید نیاز داریم که هر موقع به اسم کتاب دسترسی داشتیم، حرف اول هر کلمه بزرگ به ما تحویل داده بشه، به توابعی از این جنس، Getter گفته میشه (چون‌که داریم یک چیز رو می‌گیریم) و عموماً هدف‌ توسعه‌دهنده می‌تونه کارهای از جنس Normalization و یا Validation باشه.حالت دوم، فرض کنید هدف‌مون این هست که اسم کتابی رو که کاربر وارد کرده بررسی کنیم که حاوی کاراکتر‌های فارسی نباشه. این کار توسط توابعِ Setter انجام میشه (چون‌که داریم یک مقدار رو توی attribute ذخیره می‌کنیم) و اکثر مواقع داخل این توابع کارهای محاسباتی انجام میشه.حالت آخر، توابعی هستند که برای delete کردن یک attribute به کار می‌رن، به طور مثال ممکن هست نخواهیم اجازه بدیم کاربر بتونه اسم کتاب رو حذف کنه. اسم این توابع Deleter هست.یکم کد بزنیمبرای ساختن setter/getter توی پایتون از property کمک می‌گیریم. اولین قدم این هست که یک متد رو تعریف کنیم و این متد به عنوان Getter هست یا برای دریافت attribute مورد نظرمون (بقیه موارد setter/deleter به صورت اختیاری هستند).Firstly, define your methodبعد از تعریف متد، تنها کاری که لازم هست انجام بدیم اینه که از property decorator استفاده کنیم و پایتون مابقی کار رو برای ما انجام میده (اگر با decorator آشنا نیستید، پیشنهاد می‌کنم این پست رو بخونید).Then add property decoratorاگر به مثال بالا دقت کنید،‌ در حالی که name به صورت متد تعریف شده، اما property کاری کرده که انگار داریم با یک attribute کار می‌کنیم.نحوه‌ی تعریف setter هم به صورت پایین هست:How to use setter with propertyمثال دومفرض کنید که ما یک کلاس داریم به اسم Person که هدف‌مون این هست بعد‌ از اینکه اسم کاربر رو گرفتیم اون رو به صورت lowercase ذخیره کنیم و داده‌ی غیر از string قبول نکنیم. طبیعتا این کارها باید توسط setter انجام بشه.Example 02تا اینجای کار، میشه گفت property رفتار یک متد رو به نحوی تغییر میده که کاربر فکر می‌کنه داره با یک ویژگی/attribute کار می‌کنه. به عبارت خیلی ساده‌تر موقع اجرای متدمون نیازی به گذاشتن () نیست.از مهم‌ترین مزیت این کار هم میشه به ملموس شدن یا منطقی شدن کد برای استفاده‌ کننده‌ها اشاره کرد.دقت کنید، قرار نیست ما کاری بکنیم که متد‌ها به صورت attribute قابل دسترسی باشند! بلکه هدف اینه که کارهای محاسباتی و validation که مخصوص یک attribute هستند رو با خود attribute یکی کنیم. از دید کاربر پنهان‌شون می‌کنیم. به عبارت دیگه داریم دسترسی مستقیم رو از کاربر می‌گیریم.کد‌های قدیمیهمه‌ی ما ممکنه که به کد‌های قدیمی بر بخوریم و شاید مهم‌ترین نکته‌ی که باید بهش توجه کنیم این باشه که تغییرات ما باعث شکنندگی کد نشه (Backward compatibility رو حفظ کنیم). حالا فرض کنید قطعه کدی داریم که توسعه‌دهنده setter/getterها به صورت پایتونیک ننوشته و از طرفی ما نمی‌خوایم اینقدر کد تغییر پیدا کنه که برنامه‌های وابسته به اون از کار بیفتند. راه حل چیه؟راه حل دوباره property هست. property رو به دو حالت میشه استفاده کرد: حالت اول که حالت خیلی خوبش بود (چیزی که شما همیشه باید از اون استفاده کنید) استفاده از property decorator بود. اما حالت دومی هم وجود داره که به صورت زیر هست:Property in other wayبا این حالت نه تنها متد‌های قبلی به کارشون ادامه بدن و بلکه ما می‌تونیم با propertyی name به هدف‌مون (پایتونیک) برسیم.خستگی سلاممثل همه‌ی چیزهای کره‌زمین property هم اونقدر کامل نیست و بعضی اوقات خیلی کسل‌کننده میشه. فرض کنید هدف‌مون این هست که یک کلاس برای ذخیره کردن اطلاعات مربوط به فیلم‌ داشته باشیم‌، چیزی شبیه پایین:Movie objectاگر به کد دقت کنید، می‌بینید که چهار از attributeهای ما قرار هست که مقدار int بگیرند. از قضا، هیچ‌کدوم از اینا نمی‌تونن مقدار کمتر از صفر (منفی) بگیرند. خوشبختانه ما روش حل این مساله رو بلدیم. کلید حل مساله استفاده از property هست، کافیه هر کجا مقدار منفی خواست وارد بشه ، سریع خطا بدیم:Use property to solve the problemهمه‌چی ممکن است در نگاه اول خوب بنظر برسه، اما یک مشکلی اساسی توی این کد وجود داره و اون این هست که تکرار کد بشدت بالاست (Repetition). درسته که ما کارمون رو به درستی انجام دادیم و جلوی مقدار منفی رو گرفتیم؛ اما عملاً یک کار رو چهار بار تکرار کردیم.و خب طبق معمول، حتما باید راه بهتری برای انجام این کار باشه ?.مثلا، اگر به شکل زیر پیاده‌سازی می ‌شد، چی:Let the magic happensتوی قطعه کد بالا ما با استفاده از یک مکانیسم داریم رفتار attributeها رو مدیریت می‌کنیم. به این ساز و کار descriptor گفته میشه.تا اینجای کار به هیچ عنوان property رو تعریف نکردیم؛ بلکه کارکردش رو مرور می‌کردیم، اما واقعیت این هست که property یک نوع descriptor محسوب میشه. تو مواردی مشابه مثال بالا، ما نیاز داریم بریم ساز و کار خاصِ خودمون رو پیاده‌سازی کنیم تا به اون چیزی که می‌خوایم برسیم (NonNegative).جمع‌بندیپس تا الان متوجه شدیم که property ها functionality خیلی عجیبی به برنامه‌ی ما اضافه نمی‌کنند فقط به ما امکان مدیریت بیشتر روی attributeها رو برای ما فراهم می‌کنند.یک امکان دیگه‌ی که propertyها به ما میدن و اینجا خیلی راجع‌اش صحبت نشد، lazy loading هست. یعنی محاسبات انجام نمی‌شن تا وقتی که نیاز بشن. دلیل این‌کار هم بنظرم دیگه الان برای شما واضح هست، چون property در حقیقت دارن یک متد رو اجرا می‌کنند و درنتیجه تا اجرا نشن، محاسباتی هم نیز صورت نمی‌گیره.توی قسمت بعدی هدف‌مون بررسی و ساخت descriptor هست.از اینجا کجا بریماگر بازم دوست دارید راجعبه property بدونید، دو لینک پایین می‌تونن به شما کمک کنند.Python 101Python decorator and propertyبنده در تاریخ بیست‌ و ششم اردیبهشت ۱۳۹۸ در گروه کاربران پایتون تهران راجعبه همین موضوع یک ارائه داشتم و فایل‌هایی که اونجا استفاده شد رو توی لینک پایین می‌تونید پیدا کنید.TehPUG - Property