You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: lessons/l22.rst
+158-6Lines changed: 158 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -479,7 +479,7 @@ Class Annotations
479
479
Data Classes
480
480
----------------------------
481
481
482
-
از **نسخه 3.7 پایتون** یک ویژگی جالب به پایتون اضافه گردید. دیتا کلاس **(Data Class)** [`PEP 557 <https://www.python.org/dev/peps/pep-0557>`__]، در واقع یک سینتکسی سادهسازی شده برای ایجاد کلاسهایی که معمولا تنها حاوی Instance Attribute میباشند. این نوع کلاس با استفاده از دکوراتور ``dataclass@`` از ماژول ``dataclasses`` ایجاد میگردد [`اسناد پایتون <https://docs.python.org/3/library/dataclasses.html>`__]. برای مثال کلاس زیر را در نظر بگیرید:
482
+
از **نسخه 3.7 پایتون** یک ویژگی جالب به پایتون اضافه گردید. دیتا کلاس **(Data Class)** [`PEP 557 <https://www.python.org/dev/peps/pep-0557>`__]، در واقع سینتکسی سادهسازی شده برای ایجاد کلاسهایی میباشد که معمولا تنها حاوی Instance Attribute هستند. این نوع کلاس با استفاده از دکوراتور ``dataclass@`` از ماژول ``dataclasses`` ایجاد میگردد [`اسناد پایتون <https://docs.python.org/3/library/dataclasses.html>`__]. برای مثال کلاس زیر را در نظر بگیرید:
483
483
484
484
485
485
.. code-block:: python
@@ -510,11 +510,11 @@ Data Classes
510
510
True
511
511
512
512
513
-
در این نوع کلاس برای تعریف Attributeها از سینتکس Variable Annotations [`PEP 526 <https://www.python.org/dev/peps/pep-0526/>`__] استفاده میشود. این سینتکس و در کل ذکر نوع داده در پایتون یا Type Hints [`PEP 484 <https://www.python.org/dev/peps/pep-0484/>`__] **موضوع درس بعدی است**. در این شیوه نوع متغیرها به صراحت ذکر میگردد. در حالت عادی تعریف یک متغییر در زبان برنامهنویسی پایتون به صورت ``var = value`` میباشد (درس ششم)، همانطور که میدانیم تاکنون هیچگاه در پایتون برای تعریف متغییر نیازی به ذکر صریح نوع داده نمیبود، **اکنون نیز نیازی نیست**، ولی از **نسخه 3.6 پایتون** میتوانیم اینکار را انجام دهیم، میتوانیم نوع داده را خودمان مشخص کنیم یا به اصطلاح آن نوع را annotation کنیم. سینتکس این عملیات به صورت ``var: annotation`` میباشد، این سینتکس مشخص میکند که متغییر var از نوع annotation میباشد. همچنین با استفاده از سینتکس ``var: annotation = value`` نیز میتوان همزمان عملیات انتساب و مقداردهی را نیز انجام داد. باید توجه داشت که تغییری در ساختار مفسر پایتون ایجاد نشده است!، بلکه صرفا سینتکس جدیدی اضافه شده که میتواند به ابزارهای شخصثالث (third party) همانند IDEها برای کنترل نوع دادهها در زمان توسعه برنامه یاریرسان باشد.
513
+
در این نوع کلاس برای تعریف Attributeها از سینتکس Variable Annotations [`PEP 526 <https://www.python.org/dev/peps/pep-0526/>`__] استفاده میشود.
514
514
515
-
باید توجه داشت که طبق سند PEP 484 پیروی از اصول Type Hints در پایتون اجباری نبوده و نخواهد شد. ولی Data Class یک استثناست و در آن حتما میبایست Attributeها به شیوه شرح داده شده، تعریف گردند و به آنها فیلدهای (field) دیتا کلاس گفته میشود.
515
+
باید توجه داشت که طبق سند PEP 484 پیروی از اصول Type Hints در پایتون اجباری نبوده، نیست و نخواهد شد. ولی Data Class یک استثناست و در آن حتما میبایست Attributeها به شیوه شرح داده شده، تعریف گردند و به آنها فیلدهای (field) دیتا کلاس گفته میشود.
516
516
517
-
از آنجا که این نوع کلاس برای ایجاد یک کاربرد عمومی از کلاسها توسعه یافته (نگهداری اطلاعات)، بنابراین بسیاری از عملیاتها در آن خودکارسازی شده تا پیادهسازی این کلاس سادهتر از هر کلاس دیگری باشد. برای مثال نیازی به پیادهسازی متد ``__init__`` نیست و این متد به صورت خودکار برای کلاس ما ایجاد میگردد. اکنون اگر بخواهیم دیتاکلاس مثال قبل را به صورت عادی پیادهسازی کنیم:
517
+
از آنجا که این نوع کلاس برای ایجاد یک کاربرد عمومی از کلاسها توسعه یافته (نگهداری اطلاعات)، بنابراین بسیاری از عملیاتها در آن خودکارسازی شده تا پیادهسازی این کلاس سادهتر از هر کلاس دیگری باشد. برای مثال نیازی به پیادهسازی متد ``__init__`` نیست و این متد به صورت خودکار برای کلاس ما ایجاد میگردد (به لطف Type Hinting!). اکنون اگر بخواهیم دیتاکلاس مثال قبل را به صورت عادی پیادهسازی کنیم:
518
518
519
519
520
520
.. code-block:: python
@@ -545,7 +545,7 @@ Data Classes
545
545
False
546
546
547
547
548
-
با مقایسه این دو خروجی، مشاهده میشود که مقدار چاپ شی (سطر ۹) و نیز حاصل مقایسه دو شی (سطر ۱۴) با مقادیر یکسان متفاوت است. دلیل نیز پیشتر بیان شد، تعدادی متد خاص همانند ``__init__`` برای دیتا کلاسها پیادهسازی میشود که با پیادهسازی پیشفرض متفاوت بوده و بر نوع کاربرد این کلاسها و راحتی استفاده آنها تمرکز شده است. این پیادهسازی را میتوان به صورت زیر نمایش داد:
548
+
با مقایسه این دو خروجی، مشاهده میشود که مقدار چاپ شی (سطر ۹) و نیز حاصل مقایسه دو شی (سطر ۱۴) با مقادیر یکسان، متفاوت است. دلیل نیز پیشتر بیان شد، تعدادی متد خاص همانند ``__init__`` برای دیتا کلاسها به صورت خودکار تولید میشوند که با پیادهسازی پیشفرض متفاوت بوده و بر نوع کاربرد این کلاسها و راحتی استفاده تمرکز شده است. این پیادهسازی را میتوان به صورت زیر نمایش داد:
549
549
550
550
551
551
@@ -586,6 +586,18 @@ Data Classes
586
586
از درس پیش با متد ``__eq__`` آشنا هستیم، متد ``__str__`` [`اسناد پایتون <https://docs.python.org/3/reference/datamodel.html#object.__str__>`__] نیز یکی دیگر از متدهای خاص پایتون میباشد و هنگامی که یک شی میخواهد به نوع str تبدیل گردد، به صورت خودکار فراخوانی میگردد (**تبدیل به نوع رشته - درس هفتم**)، به صورت مشابه متد ``__repr__`` [`اسناد پایتون <https://docs.python.org/3/reference/datamodel.html#object.__repr__>`__] نیز قابل پیاده سازی است.
587
587
588
588
589
+
بهتر است مقداردهی اولیه اشیای دیتاکلاسها را به روش **نام=مقدار** انجام دهید (هنگام نمونهسازی)، در غیر این صورت اگر ترتیب تعریف فیلدها در کلاس را از بالا به پایین در نظر بگیریم، آنگاه ترتیب قرار گرفتن پارامترها در متد ``__init__`` که قرار است تولید شود، با حفظ ترتیب، از چپ به راست خواهند بود.
590
+
591
+
به همین دلیل میبایست در ترتیب قرارگرفتن فیلدهایی که دارای مقدار پیشفرض هستند دقت کرد و آنها را جزو فیلدهای انتهایی درنظر گرفت. چرا که تعریف متد ``__init__`` با خطا مواجه میگردد. از تعریف توابع به یاد داریم که پس از پارامتر با مقدار پیشفرض نمیتواند پارامتر بدون مقدار پیشفرض قرار بگیرد!
592
+
593
+
594
+
595
+
Type Hinting
596
+
~~~~~~~~~~~~~~~~~~~~~~
597
+
598
+
تنها این Attributeهای یک دیتا کلاس است که میبایست بر اساس قوانین سینتکس Type Hinting نوشته شوند. در این بین برای درج Class Attributeها نیز میبایست حتما از ``ClassVar`` استفاده گردد، در غیر این صورت آن Attribute در حکم Instance Attribute خواهد بود.
599
+
600
+
589
601
متد ``__post_init__``
590
602
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
591
603
@@ -695,7 +707,7 @@ Data Classes
695
707
* توجه: در هر فیلد تنها یکی از دو پارامتر ``default`` یا ``default_factory`` میبایست حاوی مقداری غیر از ``MISSING`` باشد.
696
708
697
709
698
-
* ``repr``, ``init``, ``compare``, ``hash``: در صورتی که هر کدام از این پارامترها برابر با مقدار ``True`` (پیشفرض) تنطیم گردند، فیلد مربوطه به متدهای ایجاد شده متناظر با هر پارامتر ارسال خواهد شد::
710
+
* ``repr``, ``init``, ``compare``, ``hash``: در صورتی که هر کدام از این پارامترها برابر با مقدار ``True`` (پیشفرض) تنظیم گردند، فیلد مربوطه به متدهای ایجاد شده متناظر با هر پارامتر ارسال خواهد شد::
699
711
700
712
repr -->> __repr__ __str__
701
713
init -->> __init__
@@ -706,6 +718,146 @@ Data Classes
706
718
* توجه چنانچه مقدار ``compare`` برای ``True`` تنظیم گردد (حالت پیشفرض)،مقدار ``hash`` میبایست ``None`` (و نه ``False``) باشد، چرا که عملیات مقایسه دو شی دیگر به مقدار hash وابسته نبوده و از طریق متدهای تولید شده (__eq__ و غیره) انجام خواهد شد.
707
719
708
720
721
+
* ``metadata``: میتوان اطلاعات اضافی و دلخواه پیرامون فیلد را در قالب یک شی دیکشنری به این پارامتر ارسال کرد.
(Field(name='id',type=<class 'int'>,default=<dataclasses._MISSING_TYPE object at 0x7f5e66a58e48>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f5e66a58e48>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD), Field(name='name',type=<class 'str'>,default=<dataclasses._MISSING_TYPE object at 0x7f5e66a58e48>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f5e66a58e48>,init=True,repr=True,hash=None,compare=False,metadata=mappingproxy({'coding': 'UTF-8'}),_field_type=_FIELD), Field(name='books',type=typing.List[__main__.Book],default=<dataclasses._MISSING_TYPE object at 0x7f5e66a58e48>,default_factory=<function get_default_books at 0x7f5e66bcb1e0>,init=True,repr=True,hash=None,compare=False,metadata=mappingproxy({}),_field_type=_FIELD))
761
+
762
+
763
+
Immutable Data Classes
764
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
765
+
766
+
دکوراتور ``dataclass@`` چندین پارامتر با مقدار پیشفرض دارد که به شرح زیر میباشند [`اسناد پایتون <https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass>`__]::
* ``init``: اگر ``True`` باشد، متد ``__init__`` تولید میشود.
774
+
775
+
* ``repr``: اگر ``True`` باشد، متد ``__repr__`` تولید میشود.
776
+
777
+
* ``order``: اگر ``True`` باشد، متدهای ``__gt__`` ،``__le__`` ،``__lt__`` و ``__ge__`` تولید میشوند.
778
+
779
+
* ``unsafe_hash``: اگر ``False`` باشد، آنگاه بر اساس مقادیر ``eq`` ،``init`` و ``frozen`` و شرایط موجود یک متد ``__hash__`` مناسب تولید میشود.
780
+
781
+
782
+
**frozen**
783
+
784
+
چنانچه این پارامتر برابر ``True`` تنظیم گردد، دیتا کلاس Immutable (غیرقابل تغییر) خواهد شد و دیگر نمیتوان مقدار هیچکدام از فیلدهای اشیای آن را پس از نمونهسازی تغییر داد، این رفتار در موارد بسیاری میتواند مفید باشد:
785
+
786
+
787
+
788
+
.. code-block:: python
789
+
:linenos:
790
+
791
+
from dataclasses import dataclass
792
+
793
+
@dataclass(frozen=True)
794
+
classPosition:
795
+
name: str
796
+
lon: float=0.0
797
+
lat: float=0.0
798
+
799
+
pos = Position('Tehran', 35.6, 51.5)
800
+
801
+
print(pos.name)
802
+
print('-'*30)
803
+
pos.name ='Qazvin'
804
+
805
+
::
806
+
807
+
Tehran
808
+
------------------------------
809
+
Traceback (most recent call last):
810
+
File "sample.py", line 13, in <module>
811
+
pos.name = 'Qazvin'
812
+
File "<string>", line 3, in __setattr__
813
+
dataclasses.FrozenInstanceError: cannot assign to field 'name'
814
+
815
+
816
+
817
+
وراثت (Inheritance)
818
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
819
+
820
+
دیتا کلاسها میتوانند از یکدیگر ارثبری داشته باشند:
821
+
822
+
823
+
.. code-block:: python
824
+
:linenos:
825
+
826
+
from dataclasses import dataclass
827
+
828
+
829
+
@dataclass
830
+
classPerson:
831
+
name: str
832
+
833
+
834
+
@dataclass
835
+
classFriend(Person):
836
+
city: str
837
+
838
+
defsay_hi(self):
839
+
print(f'Hi {self.name}')
840
+
841
+
842
+
f = Friend(city='Tehran', name='Armin')
843
+
f.say_hi()
844
+
845
+
f = Friend('Tehran', 'Armin')
846
+
f.say_hi()
847
+
848
+
::
849
+
850
+
Hi Armin
851
+
Hi Tehran
852
+
853
+
854
+
بهتر است مقداردهی اولیه اشیای دیتاکلاسها را به روش **نام=مقدار** انجام دهید، در غیر این صورت باید بدانید در هنگام ارثبری ابتدا فیلدهای supperclass مقداردهی میشوند! در نتیجه میتوان تعریف متد ``__init__`` برای کلاس ``Friend`` را برابر با تعریف زیر فرض کرد::
855
+
856
+
857
+
def __init__(self, name, city):
858
+
859
+
به همین دلیل نیز اگر یکی از فیلدهای supperclass دارای مقدار پیشفرض باشد، میبایست فیلدهای subclass نیز دارای مقدار پیشفرض باشند. چرا که تعریف متد ``__init__`` با خطا مواجه میگردد. از تعریف توابع به یاد داریم که پس از پارامتر با مقدار پیشفرض نمیتواند پارامتر بدون مقدار پیشفرض قرار بگیرد!
0 commit comments