|
1 | 1 | .. role:: emoji-size |
2 | 2 |
|
3 | 3 | .. meta:: |
4 | | - :description: کتاب آموزش زبان برنامه نویسی پایتون به فارسی، آموزش شی گرایی در پایتون، OOP در پایتون، Decorators در پایتون، Descriptors در پایتون، Properties در پایتون، دیتا کلاس در پایتون، Data Classe در پایتون |
| 4 | + :description: کتاب آموزش زبان برنامه نویسی پایتون به فارسی، آموزش شی گرایی در پایتون، OOP در پایتون، Decorators در پایتون، Descriptors در پایتون، Properties در پایتون |
5 | 5 | :keywords: آموزش, آموزش پایتون, آموزش برنامه نویسی, پایتون, Decorators, کتابخانه, پایتون, شی گرایی در پایتون, Descriptors,Properties |
6 | 6 |
|
7 | 7 |
|
8 | | -درس ۲۱: شی گرایی (OOP) در پایتون: Descriptors ،Decorator ،Data Class و Properties |
| 8 | +درس ۲۱: شی گرایی (OOP) در پایتون: Descriptors ،Decorator و Properties |
9 | 9 | =================================================================================================== |
10 | 10 |
|
11 | 11 |
|
@@ -99,238 +99,6 @@ Properties |
99 | 99 |
|
100 | 100 |
|
101 | 101 |
|
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 | | - |
334 | 102 | | |
335 | 103 |
|
336 | 104 | ---- |
|
0 commit comments