Skip to content

Commit d8f9116

Browse files
author
Saeid Darvish
committed
l21 -->split: l21,l22
1 parent d84424e commit d8f9116

3 files changed

Lines changed: 539 additions & 234 deletions

File tree

index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
lessons/l19
4848
lessons/l20
4949
lessons/l21
50+
lessons/l22
5051
log
5152
donate-report
5253
python-interactive

lessons/l21.rst

Lines changed: 2 additions & 234 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 در پایتون، دیتا کلاس در پایتون، Data Classe در پایتون
4+
:description: کتاب آموزش زبان برنامه نویسی پایتون به فارسی، آموزش شی گرایی در پایتون، OOP در پایتون، Decorators در پایتون، Descriptors در پایتون، Properties در پایتون
55
:keywords: آموزش, آموزش پایتون, آموزش برنامه نویسی, پایتون, Decorators, کتابخانه, پایتون, شی گرایی در پایتون, Descriptors,Properties
66

77

8-
درس ۲۱: شی گرایی (OOP) در پایتون: Descriptors ،Decorator ،Data Class و Properties
8+
درس ۲۱: شی گرایی (OOP) در پایتون: Descriptors ،Decorator و Properties
99
===================================================================================================
1010

1111

@@ -99,238 +99,6 @@ Properties
9999

100100

101101

102-
103-
Data Classes
104-
----------------------------
105-
106-
از **نسخه 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>`__]. برای مثال کلاس زیر را در نظر بگیرید:
107-
108-
109-
.. code-block:: python
110-
:linenos:
111-
112-
from dataclasses import dataclass
113-
114-
@dataclass
115-
class Student:
116-
name: str
117-
score: int
118-
119-
student = Student('Saeid', 70)
120-
print(student)
121-
print('-' * 30)
122-
print(student.name)
123-
print(student.score)
124-
print('-' * 30)
125-
print(Student('Saeid', 70) == Student('Saeid', 70))
126-
127-
::
128-
129-
Student(name='Saeid', score=70)
130-
------------------------------
131-
Saeid
132-
70
133-
------------------------------
134-
True
135-
136-
137-
در این نوع کلاس برای تعریف 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ها برای کنترل نوع داده‌ها در زمان توسعه برنامه یاری‌رسان باشد.
138-
139-
باید توجه داشت که طبق سند PEP 484 پیروی از اصول Type Hints در پایتون اجباری نبوده و نخواهد شد. ولی Data Class یک استثناست و در آن حتما می‌بایست Attributeها به شیوه شرح داده شده، تعریف گردند و به آن‌ها فیلدهای (field) دیتا کلاس گفته می‌شود.
140-
141-
از آنجا که این نوع کلاس برای ایجاد یک کاربرد عمومی از کلاس‌ها توسعه یافته (نگهداری اطلاعات)، بنابراین بسیاری از عملیات‌ها در آن خودکارسازی شده تا پیاده‌سازی این کلاس ساده‌تر از هر کلاس دیگری باشد. برای مثال نیازی به پیاده‌سازی متد ``__init__`` نیست و این متد به صورت خودکار برای کلاس ما ایجاد می‌گردد. اکنون اگر بخواهیم دیتاکلاس مثال قبل را به صورت عادی پیاده‌سازی کنیم:
142-
143-
144-
.. code-block:: python
145-
:linenos:
146-
147-
class Student:
148-
149-
def __init__(self, name, score):
150-
self.name = name
151-
self.score = score
152-
153-
154-
student = Student('Saeid', 70)
155-
print(student)
156-
print('-' * 30)
157-
print(student.name)
158-
print(student.score)
159-
print('-' * 30)
160-
print(Student('Saeid', 70) == Student('Saeid', 70))
161-
162-
::
163-
164-
<__main__.Student object at 0x7f922a311518>
165-
------------------------------
166-
Saeid
167-
70
168-
------------------------------
169-
False
170-
171-
172-
با مقایسه این دو خروجی، مشاهده می‌شود که مقدار چاپ شی (سطر ۹) و نیز حاصل مقایسه دو شی (سطر ۱۴) با مقادیر یکسان متفاوت است. دلیل نیز پیشتر بیان شد،‌ تعدادی متد خاص همانند ``__init__`` برای دیتا کلاس‌ها پیاده‌سازی می‌شود که با پیاده‌سازی پیش‌فرض متفاوت‌ بوده و بر نوع کاربرد این کلاس‌ها و راحتی استفاده آن‌ها تمرکز شده است. این پیاده‌سازی را می‌توان به صورت زیر نمایش داد:
173-
174-
175-
176-
.. code-block:: python
177-
:linenos:
178-
179-
class Student:
180-
181-
def __init__(self, name, score):
182-
self.name = name
183-
self.score = score
184-
185-
def __str__(self):
186-
return (f'{self.__class__.__name__}'
187-
f'(name={self.name!r}, score={self.score!r})')
188-
189-
def __eq__(self, other):
190-
return (self.name, self.score) == (other.name, other.score)
191-
192-
193-
student = Student('Saeid', 70)
194-
print(student)
195-
print('-' * 30)
196-
print(student.name)
197-
print(student.score)
198-
print('-' * 30)
199-
print(Student('Saeid', 70) == Student('Saeid', 70))
200-
201-
::
202-
203-
Student(name='Saeid', score=70)
204-
------------------------------
205-
Saeid
206-
70
207-
------------------------------
208-
True
209-
210-
از درس پیش با متد ``__eq__`` آشنا هستیم، متد ``__str__`` [`اسناد پایتون <https://docs.python.org/3/reference/datamodel.html#object.__str__>`__] نیز یکی دیگر از متدهای خاص پایتون می‌باشد و هنگامی که یک شی می‌خواهد به نوع str تبدیل گردد، به صورت خودکار فراخوانی می‌گردد (**تبدیل به نوع رشته - درس هفتم**)، به صورت مشابه متد ``__repr__`` [`اسناد پایتون <https://docs.python.org/3/reference/datamodel.html#object.__repr__>`__] نیز قابل پیاده سازی است.
211-
212-
213-
متد ``__post_init__``
214-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
215-
216-
دیتا کلاس‌ها همچنین می‌توانند شامل متد نیز باشند، چگونگی تعریف متد در دیتا کلاس تفاوتی با دیگر کلاس‌ها ندارد.
217-
218-
از طرفی می‌دانیم که متد ``__init__`` یک دیتا کلاس به صورت خودکار ایجاد می‌گردد و مرحله initialize شی از دستان ما خارج شده است. با این حال چنانچه اگر کلاس شامل متدی با نام ``__post_init__`` باشد، این متد پس از ``__init__`` به صورت خودکار فراخوانی می‌گردد:
219-
220-
.. code-block:: python
221-
:linenos:
222-
223-
from dataclasses import dataclass
224-
225-
@dataclass
226-
class Student:
227-
name: str
228-
score: int
229-
230-
def __post_init__(self):
231-
print("__post_init__ got called:", self)
232-
if self.name == 'Saeed':
233-
self.name = 'Saeid'
234-
235-
236-
student = Student('Saeed', 70)
237-
print(student)
238-
239-
::
240-
241-
__post_init__ got called: Student(name='Saeed', score=70)
242-
Student(name='Saeid', score=70)
243-
244-
245-
246-
از طریق ماژول ``dataclasses`` یک نوع یک annotation type جدید با نام ``InitVar`` در دسترس است. چنانچه در تعریف هر یک از Attributeها کلاس از این نوع استفاده کنیم، آن Attribute به عنوان پارامتر به متد ``__post_init__`` ارسال می‌گردد. باید توجه داشت که این نوع Attributeها به عنوان **Init-only variables** شناخته می‌شوند [`اسناد پایتون <https://docs.python.org/3/library/dataclasses.html#init-only-variables>`__] و مفسر پایتون آن‌ها را صرفا به ``__post_init__`` ارسال می‌کند و **جزو فیلدهای دیتا کلاس قرار نمی‌دهد**:
247-
248-
249-
.. code-block:: python
250-
:linenos:
251-
252-
253-
@dataclass
254-
class Student:
255-
name: InitVar[str]
256-
score: int
257-
258-
def __post_init__(self, name):
259-
if name == 'Saeid':
260-
self.score = 100
261-
262-
263-
student = Student('Saeid', 70)
264-
print(student)
265-
266-
267-
::
268-
269-
Student(score=100)
270-
271-
272-
تابع ``field`` و ``fields``
273-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
274-
275-
تابع ``fields`` از ماژول ``dataclasses`` یک شی از دیتا کلاس یا خود دیتا کلاس را از ورودی دریافت و یک تاپل حاوی تمام فیلد‌های آن بر می‌گرداند [`اسناد پایتون <https://docs.python.org/3/library/dataclasses.html#dataclasses.fields>`__]:
276-
277-
.. code-block:: python
278-
:linenos:
279-
280-
from dataclasses import dataclass, InitVar, fields
281-
282-
@dataclass
283-
class Student:
284-
name: str
285-
score: int = 70
286-
age: InitVar[int] = 18
287-
288-
289-
obj = Student('saeid', 90, 20)
290-
print(obj)
291-
print(fields(obj))
292-
293-
::
294-
295-
Student(name='saeid', score=90)
296-
(Field(name='name',type=<class 'str'>,default=<dataclasses._MISSING_TYPE object at 0x7f7e5c68cd68>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f7e5c68cd68>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD), Field(name='score',type=<class 'int'>,default=70,default_factory=<dataclasses._MISSING_TYPE object at 0x7f7e5c68cd68>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD))
297-
298-
299-
پیش‌تر گفتیم، Attributeهای داخل یک دیتا کلاس فیلد (Field) خوانده می‌شوند. خروجی بالا نمایش ساختار یک شی Field از دیتا کلاس می‌باشد [`اسناد پایتون <https://docs.python.org/3/library/dataclasses.html#dataclasses.field>`__]. در واقع متغیرهایی که داخل دیتا کلاس با سنتکس Variable Annotations تعریف می‌شوند، به صورت خودکار به فیلد (Field) تبدیل می‌شوند. فیلدها می‌توانند حاوی مقدار پیش‌فرض باشند (همانند فیلد ``score``). برای کاستن از حجم functionality داخل یک دیتا کلاس، ماژول ``dataclasses`` پایتون شامل تابعی است با نام ``field`` که توانایی و انعطاف زیادی در فراهم آوردن مقدار پیش‌فرض برای فیلدهای تعریف شده ایجاد می‌کند.
300-
301-
یک شی فیلد شامل پارامترهایی است که از طریق تابع ``field`` قابل تنظیم هستند، البته به جز دو پارامتر زیر که از تعریف Variable Annotations استنباط می‌شوند:
302-
303-
304-
* ``name``: نام فیلد
305-
306-
* ``type``: نوع (type) فیلد
307-
308-
**تعریف مقدار پیش‌فرض برای یک فیلد با استفاده از تابع** ``field``::
309-
310-
field(*, default=MISSING, default_factory=MISSING, repr=True, hash=None, init=True, compare=True, metadata=None)
311-
312-
* توجه:‌ همانطور که از مبحث Keyword-Only Arguments از درس دوازدهم به یاد داریم، فراخوانی این تابع تنها با استفاده از ارسال آرگومان به صورت نام=مقدار مجاز خواهید بود.
313-
314-
315-
* ``default``: مقدار پیش‌فرض فیلد، در صورت عدم نیاز می‌بایست با مقدار ویژه ``MISSING`` مقداردهی گردد.
316-
317-
* ``default_factory``: یک موجودیت callable بدون آرگومان را دریافت می‌کند و در زمانی که به مقدار پیش‌فرض برای فیلد نیاز باشد، فراخوانی می‌گردد. در صورت عدم نیاز می‌بایست با مقدار ویژه ``MISSING`` مقداردهی گردد. به بیانی دیگر می‌توان با استفاده از این پارامتر،‌ یک تابع به فیلد اختصاص داد که مقدار یا مقادیر پیش‌فرضی را برای فیلد مورد نظر تولید نماید.
318-
319-
* توجه: در هر فیلد تنها یکی از دو پارامتر ``default`` یا ``default_factory`` می‌بایست حاوی مقداری غیر از ``MISSING`` باشد.
320-
321-
322-
* ``repr``, ``init``, ``compare``, ``hash``: در صورتی که هر کدام از این پارامتر‌ها برابر با مقدار ``True`` (پیش‌فرض) تنطیم گردند، فیلد مربوطه به متدهای ایجاد شده متناظر با هر پارامتر ارسال خواهد شد::
323-
324-
repr -->> __repr__ __str__
325-
init -->> __init__
326-
compare -->> __eq__ __ne__ __lt__ __le__ __gt__ __ge__
327-
hash -->> __hash__
328-
329-
330-
* توجه چنانچه مقدار ``compare`` برای ``True`` تنظیم گردد (حالت پیش‌فرض)،‌مقدار ``hash`` می‌بایست ``None`` (و نه ``False``) باشد، چرا که عملیات مقایسه دو شی دیگر به مقدار hash وابسته نبوده و از طریق متدهای تولید شده (__eq__ و غیره) انجام خواهد شد.
331-
332-
333-
334102
|
335103
336104
----

0 commit comments

Comments
 (0)