|
1 | 1 | .. role:: emoji-size |
2 | 2 |
|
3 | 3 | .. meta:: |
4 | | - :description: کتاب آموزش زبان برنامه نویسی پایتون به فارسی، آموزش شی گرایی در پایتون، OOP در پایتون، Decorators در پایتون، Descriptors در پایتون، Properties در پایتون |
5 | | - :keywords: آموزش, آموزش پایتون, آموزش برنامه نویسی, پایتون, Decorators, کتابخانه, پایتون, شی گرایی در پایتون, Descriptors,Properties |
| 4 | + :description: کتاب آموزش زبان برنامه نویسی پایتون به فارسی، آموزش شی گرایی در پایتون، OOP در پایتون، Decorators در پایتون، Descriptors در پایتون، property@ در پایتون |
| 5 | + :keywords: آموزش, آموزش پایتون, آموزش برنامه نویسی, پایتون, Decorators, کتابخانه, پایتون, شی گرایی در پایتون, Descriptors,property@ |
6 | 6 |
|
7 | 7 |
|
8 | | -درس ۲۱: شی گرایی (OOP) در پایتون: __Descriptors ،Decorator ،__slots و Properties |
| 8 | +درس ۲۱: شی گرایی (OOP) در پایتون: __Descriptors ،Decorator ،__slots و property@ |
9 | 9 | =================================================================================================== |
10 | 10 |
|
11 | 11 |
|
@@ -473,20 +473,275 @@ Decorators |
473 | 473 | Descriptors |
474 | 474 | ---------------------------- |
475 | 475 |
|
| 476 | +توصیفگر (Descriptor) کلاسی است که کنترل عملیاتهای دریافت (get)، تنظیم (set) و حذف (delete) را بر روی یک attribute از شیای دیگر را فراهم میکند. Descriptor یک راهکار پایتونی (Pythonic) برای ایجاد مکانیزم get & set رایج در دیگر زبانهای برنامهنویسی میباشد. |
476 | 477 |
|
| 478 | +**چگونه میتوان یک Descriptor در پایتون ایجاد کرد؟** [`اسناد پایتون <https://docs.python.org/3/reference/datamodel.html#implementing-descriptors>`__] |
477 | 479 |
|
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@ |
479 | 638 | ---------------------------- |
480 | 639 |
|
| 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:: |
481 | 735 |
|
| 736 | + از ``property@`` تنها برای Instance Attributeها میتوان استفاده کرد. |
482 | 737 |
|
483 | 738 | | |
484 | 739 |
|
485 | 740 | ---- |
486 | 741 |
|
487 | 742 | :emoji-size:`😊` امیدوارم مفید بوده باشه |
488 | 743 |
|
489 | | -`لطفا دیدگاه و سوالهای مرتبط با این درس خود را در کدرز مطرح نمایید. <https://www.coderz.ir/python-tutorial-oop-descriptors-properties>`_ |
| 744 | +`لطفا دیدگاه و سوالهای مرتبط با این درس خود را در کدرز مطرح نمایید. <https://www.coderz.ir/python-tutorial-oop-slots-descriptors-property>`_ |
490 | 745 |
|
491 | 746 |
|
492 | 747 |
|
0 commit comments