Skip to content

Commit ef7088a

Browse files
author
Saeid Darvish
committed
l21: complete property
1 parent c65c396 commit ef7088a

2 files changed

Lines changed: 261 additions & 6 deletions

File tree

_templates/sphinx_minoo_theme/includes/header.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<meta charset="utf-8">
33
<meta name="viewport" content="width=device-width, initial-scale=1">
44
<meta name="google-site-verification" content="IK2uIb0qP_6M9oopAJzjrXSMXGbyifQfX7nRlr2b0vI" />
5-
<link rel="alternate" href="https://python.coderz.ir" hreflang="fa-ir" />
5+
<link rel="alternate" href="https://python.coderz.ir/{{ pagename }}.html" hreflang="fa-ir" />
66

77
{{ metatags }}
88

lessons/l21.rst

Lines changed: 260 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
.. role:: emoji-size
22

33
.. meta::
4-
:description: کتاب آموزش زبان برنامه نویسی پایتون به فارسی، آموزش شی گرایی در پایتون، OOP در پایتون، Decorators در پایتون، Descriptors در پایتون، Properties در پایتون
5-
:keywords: آموزش, آموزش پایتون, آموزش برنامه نویسی, پایتون, Decorators, کتابخانه, پایتون, شی گرایی در پایتون, Descriptors,Properties
4+
:description: کتاب آموزش زبان برنامه نویسی پایتون به فارسی، آموزش شی گرایی در پایتون، OOP در پایتون، Decorators در پایتون، Descriptors در پایتون، property@ در پایتون
5+
:keywords: آموزش, آموزش پایتون, آموزش برنامه نویسی, پایتون, Decorators, کتابخانه, پایتون, شی گرایی در پایتون, Descriptors,property@
66

77

8-
درس ۲۱: شی گرایی (OOP) در پایتون: __Descriptors ،Decorator ،__slots و Properties
8+
درس ۲۱: شی گرایی (OOP) در پایتون: __Descriptors ،Decorator ،__slots و property@
99
===================================================================================================
1010

1111

@@ -473,20 +473,275 @@ Decorators
473473
Descriptors
474474
----------------------------
475475

476+
توصیف‌گر (Descriptor) کلاسی است که کنترل عملیات‌های دریافت (get)، تنظیم (set) و حذف (delete) را بر روی یک attribute از شی‌ای دیگر را فراهم می‌کند. Descriptor یک راهکار پایتونی (Pythonic) برای ایجاد مکانیزم get & set رایج در دیگر زبان‌های برنامه‌نویسی می‌باشد.
476477

478+
**چگونه می‌توان یک Descriptor در پایتون ایجاد کرد؟** [`اسناد پایتون <https://docs.python.org/3/reference/datamodel.html#implementing-descriptors>`__]
477479

478-
Properties
480+
۱- یک کلاس ایجاد کنیم که در آن حداقل یکی از متدهای خاص ``__set__`` ،``__get__`` و ``__delete__`` بازپیاده‌سازی (یا بهتر است بگوییم Override) شود.
481+
482+
۲- از شی این کلاس به عنوان attribute مناسب از کلاس مورد نظر استفاده کنیم.
483+
484+
485+
**کاربرد Descriptor پایتون چیست؟**
486+
487+
هر زمان بخواهیم رویدادهایی همچون دریافت (get)، تنظیم (set) و حذف (delete) را بر روی یک attribute کنترل کنیم. برای مثال کلاسی شامل یک attribute با نام ایمیل (email) است، می‌خواهیم پیش از تنظیم مقدار بر روی این فیلد، مقدار جدید به صورت خودکار اعتبارسنجی (Validation) شود و در صورت صحت عملیات انجام شود:
488+
489+
490+
.. code-block:: python
491+
:linenos:
492+
493+
494+
import re
495+
496+
class EmailField:
497+
498+
def __init__(self, email=None):
499+
self.email = email
500+
501+
def __get__(self, instance, owner=None):
502+
print('-' * 10, 'CALLED[__get__]')
503+
print('instance:', instance)
504+
print('owner:', owner)
505+
print('-' * 30)
506+
print()
507+
508+
return self.email
509+
510+
def __set__(self, instance, value):
511+
print('-' * 10, 'CALLED[__set__]')
512+
print('instance:', instance)
513+
print('value:', value)
514+
print('-' * 30)
515+
516+
if re.match('^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$', value):
517+
self.email = value
518+
print('Successful!\n')
519+
else:
520+
print(f'{value} is not a valid email!\n')
521+
522+
523+
class Student:
524+
email = EmailField()
525+
526+
527+
obj = Student()
528+
529+
email = obj.email # CALLED[__get__]
530+
531+
obj.email = 'python$$1400' # CALLED[__set__]
532+
533+
obj.email = 'python@coderz.ir' # CALLED[__set__]
534+
535+
print(obj.email) # CALLED[__get__]
536+
537+
538+
::
539+
540+
---------- CALLED[__get__]
541+
instance: <__main__.Student object at 0x7f828bb9f4e0>
542+
owner: <class '__main__.Student'>
543+
------------------------------
544+
545+
---------- CALLED[__set__]
546+
instance: <__main__.Student object at 0x7f828bb9f4e0>
547+
value: python$$1400
548+
------------------------------
549+
python$$1400 is not a valid email!
550+
551+
---------- CALLED[__set__]
552+
instance: <__main__.Student object at 0x7f828bb9f4e0>
553+
value: python@coderz.ir
554+
------------------------------
555+
Successful!
556+
557+
---------- CALLED[__get__]
558+
instance: <__main__.Student object at 0x7f62e42c64e0>
559+
owner: <class '__main__.Student'>
560+
------------------------------
561+
562+
python@coderz.ir
563+
564+
565+
در نمونه کد، بالا کلاس ``EmailField`` یک Descriptor برای اتریبیوت ``email`` از کلاس ``Student`` می‌باشد. همانطور که مشاهده می‌شود، هرگاه مقداری به ``email`` انتساب داده می‌شود (سطرهای ۳۸ و ۴۰)، به صورت خودکار متد ``__set__`` از کلاس Descriptor آن فراخوانی می‌گردد و به همین ترتیب هرگاه مقدار آن درخواست می‌گردد (سطرهای ۳۶ و ۴۲)، متد ``__get__`` فراخوانی می‌گردد.
566+
567+
پیشنهاد می‌شود در صورت امکان مقدار attribute را توسط Descriptor نگهداری نکنید و از Descriptor تنها برای انجام عملیات‌ مربوطه استفاده نمایید. بنابراین مثال قبل را می‌توانیم به صورت زیر بازنویسی نماییم:
568+
569+
570+
.. code-block:: python
571+
:linenos:
572+
573+
import re
574+
575+
class EmailField:
576+
577+
def __init__(self, attr_name):
578+
self.attr_name = attr_name
579+
580+
def __get__(self, instance, owner=None):
581+
return instance.__dict__.get(self.attr_name)
582+
583+
def __set__(self, instance, value):
584+
if re.match('^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$', value):
585+
instance.__dict__[self.attr_name] = value
586+
587+
588+
class Student:
589+
email = EmailField('email')
590+
591+
592+
obj = Student()
593+
obj.email = 'python@coderz.ir'
594+
595+
print(obj.email)
596+
597+
598+
::
599+
600+
python@coderz.ir
601+
602+
603+
در این روش تنها نام attribute نگهداری و از آن برای دستیابی به مقدار آن attribute، از طریق خود شی اقدام کردیم.
604+
605+
اگر از **نسخه 3.6 به بعد پایتون** بهره‌مند هستید،‌ با استفاده از متد ``__set_name__`` [`اسناد پایتون <https://docs.python.org/3/reference/datamodel.html#object.__set_name__>`__] در کلاس Descriptor، دیگر حتی نیازی به پیاده‌سازی متد ``__init__`` و ارسال دستی نام attribute هم نخواهد بود:
606+
607+
608+
.. code-block:: python
609+
:linenos:
610+
611+
import re
612+
613+
class EmailField:
614+
615+
def __set_name__(self, owner, name):
616+
self.attr_name = name
617+
618+
def __get__(self, instance, owner=None):
619+
return instance.__dict__.get(self.attr_name)
620+
621+
def __set__(self, instance, value):
622+
if re.match('^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$', value):
623+
instance.__dict__[self.attr_name] = value
624+
625+
626+
class Student:
627+
email = EmailField()
628+
629+
630+
.. tip::
631+
632+
از Descriptor تنها برای Class Attributeها می‌توان استفاده کرد.
633+
634+
635+
636+
637+
property@
479638
----------------------------
480639

640+
خیلی ساده، این دکوراتور (``property@``) را می‌توان یک Descriptor سطح بالا دانست که توسط کتابخانه استاندارد پایتون برای Instance Attributeها فراهم آورده شده است. به نمونه کد زیر توجه نمایید:
641+
642+
.. code-block:: python
643+
:linenos:
644+
645+
import re
646+
647+
class Contact:
648+
649+
def __init__(self, name, phone):
650+
self._name = name
651+
self._phone = phone
652+
653+
@property
654+
def name(self):
655+
return self._name
656+
657+
@name.setter
658+
def name(self, new_name):
659+
if new_name and len(new_name) > 0:
660+
self._name = new_name
661+
else:
662+
print("Please enter a valid name")
663+
664+
@name.deleter
665+
def name(self):
666+
del self._name
667+
668+
@property
669+
def phone(self):
670+
return self._phone
671+
672+
673+
@phone.setter
674+
def phone(self, new_phone):
675+
if re.match(r'^09\d{9}$', new_phone):
676+
self._phone = new_phone
677+
else:
678+
print("Please enter a valid phone")
679+
680+
@phone.deleter
681+
def phone(self):
682+
del self._phone
683+
684+
685+
obj = Contact(name='Saeid', phone='09999999999')
686+
687+
obj.phone = '09123456'
688+
print('-' * 30)
689+
print(obj.name)
690+
print(obj.phone)
691+
692+
693+
::
694+
695+
Please enter a valid phone
696+
------------------------------
697+
Saeid
698+
09999999999
699+
700+
701+
در این مثال، کلاس ``Contact`` حاوی دو Instance Attribute با نام‌های ``name`` و ``phone`` می‌باشد. برای اینکه بتوانیم رویدادهایی همچون دریافت (get)، تنظیم (set) و حذف (delete) را بر روی آن‌ها کنترل کنیم، از دکوراتور ``property@`` استفاده کردیم. به این صورت که:
702+
703+
**۱-** نخست باید توجه داشت که نام Attributeها با یک کاراکتر ``_`` شروع کردیم. با این کار به دیگر برنامه‌نویسان خواهیم گفت که این Attribute با سطح دسترسی protected می‌باشد (درس بیستم)::
704+
705+
def __init__(self, name, phone):
706+
self._name = name
707+
self._phone = phone
708+
709+
**۲-** برای هر کدام یک متد getter ساختیم و به آن دکوراتور ``property@`` انتساب دادیم. نام این متد را همنام با Attributeها ولی بدون ``_`` انتخاب کردیم::
710+
711+
@property
712+
def name(self):
713+
return self._name
714+
715+
@property
716+
def phone(self):
717+
return self._phone
718+
719+
720+
نام این متد هر چیزی انتخاب شود، در زمان درخواست مقدار Attribute باید از این نام (به جای نام اصلی Attribute) استفاده گردد (سطرهای ۴۵ و ۴۶).
721+
722+
**۳-** اکنون می‌توانیم دو متد دیگر برای عملیات set و delete پیاده‌سازی کنیم و به آن‌ها دکوراتورهای زیر را انتساب دهیم::
723+
724+
@<property_getter_method_name>.setter
725+
@<property_getter_method_name>.deleter
726+
727+
728+
بخش نخست از نام دکوراتور (property_getter_method_name) می‌بایست همان نام متد getter باشد.
729+
730+
در این مثال ما از همان نام متد getter برای نام‌گذاری این دو متد استفاده کردیم. ولی باید توجه داشته باشید که نام این دو متد هر چیزی انتخاب شود، در زمان تنظیم مقدار (سطر ۴۳) یا حذف Attribute باید از این نام (به جای نام اصلی Attribute) استفاده گردد.
731+
732+
733+
734+
.. tip::
481735

736+
از ``property@`` تنها برای Instance Attributeها می‌توان استفاده کرد.
482737

483738
|
484739
485740
----
486741

487742
:emoji-size:`😊` امیدوارم مفید بوده باشه
488743

489-
`لطفا دیدگاه و سوال‌های مرتبط با این درس خود را در کدرز مطرح نمایید. <https://www.coderz.ir/python-tutorial-oop-descriptors-properties>`_
744+
`لطفا دیدگاه و سوال‌های مرتبط با این درس خود را در کدرز مطرح نمایید. <https://www.coderz.ir/python-tutorial-oop-slots-descriptors-property>`_
490745

491746

492747

0 commit comments

Comments
 (0)