Skip to content

13. کلاس‌های عمومی [BaseEntity] و [MyException]

اکنون دو کلاس را تعریف می‌کنیم که از این پس به‌طور منظم از آن‌ها استفاده خواهیم کرد.

Image

13.1. کلاس MyException

کلاس [MyException] (MyException.py) یک کلاس استثنای سفارشی را فراهم می‌کند:


#یک کلاس استثنای سفارشی مشتق از [BaseException]
class MyException(BaseException):
    # سازنده
    def __init__(self: object, code: int, message: str):
        # پدر
        BaseException.__init__(self, message)
        # کد خطا
        self.code = code

    # toString
    def __str__(self):
        return f"MyException[{self.code}, {super().__str__()}]"

    # گیرنده
    @property
    def code(self) -> int:
        return self.__code

    # setter
    @code.setter
    def code(self, code: int):
        # کد خطا باید یک عدد صحیح مثبت باشد
        if isinstance(code, int) and code > 0:
            self.__code = code
        else:
            # استثناء
            raise BaseException(f"code erreur {code} incorrect")

یادداشت‌ها

  • خط ۲: کلاس [MyException] از کلاس از پیش تعریف شده [BaseException] مشتق شده است؛
  • خط ۴: سازنده دو پارامتر را می‌پذیرد:
    • [code]: یک کد خطای عددی؛
    • [message]: یک پیام خطا؛
  • خط ۶: پیام خطا به کلاس والد ارسال می‌شود؛
  • خطوط ۱۴–۲۷: ویژگی [code] از طریق گتر/ستتر دستکاری می‌شود؛
  • خطوط ۲۳–۲۴: اعتبار ویژگی [code] بررسی می‌شود: باید یک عدد صحیح بزرگ‌تر از ۰ باشد؛

13.2. کلاس [BaseEntity]

کلاس [BaseEntity] کلاس والد اکثر کلاس‌هایی خواهد بود که ما برای محصورسازی اطلاعات یک شیء ایجاد می‌کنیم. از این پس، ما عمدتاً از دو نوع کلاس استفاده خواهیم کرد:

  • کلاس‌هایی که تنها هدفشان گردآوری اطلاعات مربوط به یک شیء واحد در یک مکان است. این کلاس‌ها هیچ رفتاری (متدی) به جز گترها/ستورها و یک تابع نمایش (__str__) نخواهند داشت. اگر N شیء برای مدیریت وجود داشته باشد، این کلاس‌ها N بار نمونه برداری می‌شوند. [BaseEntity] کلاس پایه برای این نوع کلاس خواهد بود؛
  • کلاس‌هایی که نقش اصلی آن‌ها دربرگیری متدها و اطلاعات بسیار اندک است. این کلاس‌ها تنها یک بار نمونه سازی می‌شوند (singleton). نقش آن‌ها پیاده‌سازی الگوریتم‌های یک برنامه کاربردی است؛

کلاس [BaseEntity] به شرح زیر است:


# واردات‌ها
import json
import re

from MyException import MyException


class BaseEntity(object):
    # ویژگی‌های مستثنی‌شده از وضعیت کلاس
    excluded_keys = []

    # ویژگی‌های کلاس
    @staticmethod
    def get_allowed_keys() -> list:
        # id: شناسه‌ی شیء
        return ["id"]

    # toString
    def __str__(self) -> str:
        return self.asjson()

    # گیرنده
    @property
    def id(self) -> int:
        return self.__id

    # setter
    @id.setter
    def id(self, id):
        # شناسه باید یک عدد صحیح ≥ 0 باشد
        try:
            id = int(id)
            erreur = id < 0
        except:
            erreur = True
        #خطا؟
        if erreur:
            raise MyException(1, f"L'identifiant d'une entité {self.__class__} doit être un entier >=0")
        else:
            self.__id = id

    def fromdict(self, state: dict, silent=False):
        

    def set_value(self, key: str, value, new_attributes) -> dict:
        

    def asdict(self, included_keys: list = None, excluded_keys: list = []) -> dict:
        

    def asjson(self, excluded_keys: list = []) -> str:
        

    def fromjson(self, json_state: str):
        

نظرات

  • هدف کلاس [BaseEntity] تسهیل تبدیل‌ها بین Object و Dictionary و بین Object و jSON است. بنابراین متدهای زیر ارائه شده‌اند:
    • [asdict]: دیکشنری ویژگی‌های شیء را بازمی‌گرداند؛
    • [fromdict]: یک شیء را از روی یک دیکشنری ایجاد می‌کند؛
    • [asjson]: رشته jSON را برای شی برمی‌گرداند، همانند تابع [__str__]؛
    • [fromjson]: یک شیء را از رشتهٔ آن jSON می‌سازد؛
  • کلاس [BaseEntity] برای ارث‌بری در نظر گرفته شده است، نه اینکه به همین شکل استفاده شود؛
  • خطوط ۲۲–۲۵: کلاس [BaseEntity] تنها یک ویژگی دارد، عدد صحیح [id]. این ویژگی شناسهٔ شیء است. در عمل، اغلب مفید است که بتوان بین نمونه‌های یک کلاس تمایز قائل شد. ما این کار را با استفاده از این ویژگی انجام خواهیم داد که برای هر نمونه منحصربه‌فرد است. علاوه بر این، اشیاء اغلب از پایگاه‌های داده سرچشمه می‌گیرند که در آنجا با یک کلید اصلی، معمولاً یک عدد صحیح، شناسایی می‌شوند. در چنین مواردی، [id] کلید اصلی خواهد بود؛
  • خطوط ۲۷–۴۰: متد setter برای ویژگی [id]. ما بررسی می‌کنیم که این یک عدد صحیح بزرگ‌تر یا مساوی با ۰ است. اگر اینطور نباشد، یک استثنا از نوع [MyException] پرتاب می‌شود (خط ۳۹);
  • خط ۱۰: [excluded_keys] یک ویژگی کلاس است، نه ویژگی نمونه. بنابراین، ما [BaseEntity.excluded_keys] را خواهیم نوشت. این ویژگی کلاس، فهرستی است که شامل ویژگی‌های کلاسی می‌شود که در تبدیل‌های Object / Dictionary و Object / jSON شرکت نمی‌کنند؛
  • خطوط ۱۲–۱۶: [get_allowed_keys] فهرست ویژگی‌های کلاس را بازمی‌گرداند. در تبدیل Dictionary → Object یا jSON → Object، تنها کلیدهای موجود در این فهرست پذیرفته می‌شوند. هر کلاسی که از کلاس [BaseEntity] ارث می‌برد، باید این لیست را مجدداً تعریف کند؛

در اینجا مهم است که درک کنیم ویژگی‌ها و توابع کلاس [BaseEntity] برای کلاس‌های مشتق‌شده از [BaseEntity] قابل دسترسی هستند. این نکته کلیدی است که باید درک شود.

اکنون کد کلاس [BaseEntity] را به تفصیل بررسی خواهیم کرد. این کد نسبتاً پیشرفته است. خوانندگان مبتدی می‌توانند صرفاً توضیح نقش هر تابع را بدون ورود به خود کد مطالعه کنند.

13.2.1. متد [BaseEntity.fromdict]

13.2.1.1. Définition

متد [fromdict] به شما امکان می‌دهد یک شیء [BaseEntity] یا یک شیء مشتق‌شده را از یک دیکشنری مقداردهی اولیه کنید:


def fromdict(self, state: dict, silent=False):
        # شیء در حال به‌روزرسانی است
        # کلیدهای مجاز
        allowed_keys = self.__class__.get_allowed_keys()
        # تکرار روی کلیدهای حالت
        for key, value in state.items():
            #آیا کلید مجاز است؟
            if key not in allowed_keys:
                if not silent:
                    raise MyException(2, f"la clé {key} n'est pas autorisée")
            else:
                # تلاش برای تخصیص مقدار به کلید
                # هر استثنا مجاز به انتشار است
                setattr(self, key, value)
        # بازگرداندن شیء
        return self

توضیحات

  • خط ۱: این تابع دیکشنری [state] را به عنوان پارامتر دریافت می‌کند که از آن شیء فعلی مقداردهی اولیه خواهد شد؛
  • خط ۴: تابع استاتیک [get_allowed_keys] از کلاسی فراخوانی می‌شود که تابع [fromdict] را فراخوانی کرده است. اگر با کلاسی سروکار داشته باشیم که از [BaseEntity] ارث‌بری شده باشد و آن کلاس فرعی، تابع استاتیک [get_allowed_keys] را بازتعریف کرده باشد، آنگاه تابع [get_allowed_keys] فراخوانی می‌شود. هر کلاس مشتق، این تابع استاتیک را برای اعلام خواص خود بازتعریف می‌کند؛
  • خط ۶: کلیدها و مقادیر دیکشنری [state] به صورت متوالی بررسی می‌شوند؛
  • خط ۸: اگر کلید [key] یکی از ویژگی‌های کلاس نباشد، آنگاه یا:
    • نادیده گرفته می‌شود؛
    • یک استثنا پرتاب می‌شود (خط ۱۰). توسعه‌دهنده نیاز خود را با ارسال پارامتر صحیح [silent] (خط 1) مشخص می‌کند. مقدار پیش‌فرض [silent] به این معناست که اگر تلاشی برای مقداردهی اولیه شیء با خاصیتی که ندارد صورت گیرد، یک استثنا پرتاب می‌شود؛
  • خط ۱۴: اگر کلید یکی از ویژگی‌های شیء باشد، آنگاه با استفاده از تابع از پیش تعریف‌شده [setattr] به شیء [self] تخصیص داده می‌شود؛
  • خط 16: تابع، شیء اولیه شده را بازمی‌گرداند؛

13.2.1.2. Exemples

Image

13.2.1.2.1. کلاس [Utils]

کلاس [Utils] (Utils.py) به شرح زیر است:


class Utils:
    # متد استاتیک
    @staticmethod
    def is_string_ok(string: str) -> bool:
        #آیا `string` یک رشته است؟
        erreur = not isinstance(string, str)
        if not erreur:
            #آیا رشته خالی است؟
            erreur = string.strip() == ''
        # نتیجه
        return not erreur

در خطوط ۳–۱۱، یک متد استاتیک تعریف شده است که اگر پارامتر آن [str] یک رشته غیرخالی باشد، مقدار بولی true را برمی‌گرداند؛

13.2.1.2.2. کلاس [Personne]

کلاس [Personne] (Personne.py) از کلاس [BaseEntity] ارث می‌برد:


# واردات‌ها
from BaseEntity import BaseEntity
from MyException import MyException
from Utils import Utils


#کلاس Person
class Personne(BaseEntity):
    # ویژگی‌های حذف‌شده از وضعیت کلاس
    excluded_keys = []

    # ویژگی‌های کلاس
    # شناسه: شناسه‌ی شخص
    # نام: نام شخص
    # نام خانوادگی: نام خانوادگی شخص
    # سن: سن شخص
    @staticmethod
    def get_allowed_keys() -> list:
        # شناسه: شناسه‌ی شیء
        return BaseEntity.get_allowed_keys() + ["nom", "prénom", "âge"]

    # گیرنده‌ها
    @property
    def prénom(self) -> str:
        return self.__prénom

    @property
    def nom(self) -> str:
        return self.__nom

    @property
    def âge(self) -> int:
        return self.__âge

    # setورها
    @prénom.setter
    def prénom(self, prénom: str):
        # نام اول نباید خالی باشد
        if Utils.is_string_ok(prénom):
            self.__prénom = prénom.strip()
        else:
            raise MyException(11, "Le prénom doit être une chaîne de caractères non vide")

    @nom.setter
    def nom(self, nom: str):
        # نام اول نباید خالی باشد
        if Utils.is_string_ok(nom):
            self.__nom = nom.strip()
        else:
            raise MyException(12, "Le nom doit être une chaîne de caractères non vide")

    @âge.setter
    def âge(self, âge: int):
        # سن باید یک عدد صحیح ≥ 0 باشد
        erreur = False
        if isinstance(âge, int):
            if âge >= 0:
                self.__âge = âge
            else:
                erreur = True
        else:
            erreur = True
        # خطا؟
        if erreur:
            raise MyException(13, "L'âge doit être un entier >=0")
  • خط ۸: کلاس [Personne] از کلاس [BaseEntity] مشتق شده است؛
  • خطوط ۸–۶۵: بیشتر کلاس [Personne] که قبلاً با آن مواجه شده‌ایم، حفظ شده است. تفاوت‌ها به شرح زیر است:
    • این کلاس دیگر سازنده ندارد؛
    • این کلاس از استثنای [MyException] استفاده می‌کند، برای مثال در خط ۶۵؛
    • این کلاس یک متد استاتیک به نام [get_allowed_keys] (خطوط 17–20) دارد که لیست ویژگی‌های آن را تعریف می‌کند. ویژگی‌های خاص کلاس [Personne] به ویژگی‌های کلاس والد [BaseEntity] اضافه می‌شوند؛
    • این کلاس دارای یک لیست ایستا به نام [excluded_keys] است که بعداً به آن باز خواهیم گشت؛
13.2.1.2.3. کلاس [Enseignant]

کلاس [Enseignant] (Enseignant.py) از کلاس [Personne] ارث می‌برد:


# واردات
from MyException import MyException
from Personne import Personne
from Utils import Utils


#کلاس معلم
class Enseignant(Personne):
    # ویژگی‌های حذف‌شده از نمونه کلاس
    excluded_keys = []

    # ویژگی‌های کلاس
    # id: شناسه‌ی شخص
    # نام: نام شخص
    # نام خانوادگی: نام خانوادگی شخص
    # سن: سن شخص
    # موضوع: موضوع تدریس‌شده
    @staticmethod
    def get_allowed_keys() -> list:
        # شناسه: شناسه‌ی شیء
        return Personne.get_allowed_keys() + ["discipline"]

    # ویژگی‌ها
    @property
    def discipline(self) -> str:
        return self.__discipline

    @discipline.setter
    def discipline(self, discipline: str):
        # موضوع باید یک رشته غیرخالی باشد
        if Utils.is_string_ok(discipline):
            self.__discipline = discipline
        else:
            raise MyException(21, "La discipline doit être une chaîne de caractères non vide")

    # روش نمایش
    def show(self):
        print(f"Enseignant[{self.id}, {self.prénom}, {self.nom}, {self.âge}]")
  • خط ۸: کلاس [Enseignant] از کلاس [Personne] ارث می‌برد؛
  • خطوط ۱۸–۲۱: لیست ویژگی‌های کلاس را تعریف می‌کنند؛
  • خطوط ۳۷–۳۸: متد [show] هویت معلم را نمایش می‌دهد؛
13.2.1.2.4. پیکربندی [config]

اسکریپت‌های مثال از پیکربندی زیر استفاده می‌کنند: [config]:


def configure():
    import os

    # مسیر فایل پیکربندی
    script_dir = os.path.dirname(os.path.abspath(__file__))

    # مسیرهای مطلق دایرکتوری‌هایی که باید در syspath گنجانده شوند
    absolute_dependencies = [
        #کلاس BaseEntity
        f"{script_dir}/entities",
    ]

    # به‌روزرسانی syspath
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    #پیکربندی بازیابی می‌شود
    return {}
  • خطوط ۸–۱۰: پوشه‌هایی که وابستگی‌های پروژه را در خود دارند؛
  • خطوط ۱۴–۱۵: مسیر پایتون ساخته می‌شود؛
  • خط ۱۸: یک دیکشنری خالی بازگردانده می‌شود (هیچ پیکربندی دیگری به جز syspath وجود ندارد)؛
13.2.1.2.5. اسکریپت [fromdict_01]

اسکریپت [fromdict_01] به شرح زیر است:


#پیکربندی برنامه
import config

config = config.configure()

#مسیر سیستم پیکربندی شده است – اکنون می‌توانیم واردات را انجام دهیم
from Enseignant import Enseignant

# یک معلم
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56})
enseignant1.show()
  • خط ۱۰: یک شیء از نوع [Enseignant] از یک دیکشنری ایجاد می‌شود. برای این کار، از سازندهٔ پیش‌فرض کلاس برای ایجاد یک شیء از نوع [Enseignant] استفاده می‌شود که سپس متد [fromdict] روی آن اعمال می‌گردد. مهم است بدانیم که در این مورد، متد [fromdict] که اجرا می‌شود، متعلق به کلاس والد [BaseEntity] است. در واقع:
    • ابتدا متد [fromdict] در کلاس [Enseignant] جستجو می‌شود. این متد وجود ندارد؛
    • سپس در کلاس والد [Personne] جستجو می‌شود. وجود ندارد؛
    • سپس در کلاس والد [BaseEntity] جستجو می‌شود. وجود دارد؛
  • خط ۱۱: شیء [Enseignant] نمایش داده می‌شود؛

نتایج به شرح زیر است:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/fromdict_01.py
Enseignant[1, paul, lourou, 56]

Process finished with exit code 0
13.2.1.2.6. اسکریپت [fromdict_02]

اسکریپت [fromdict_02] به شرح زیر است:


# در حال پیکربندی برنامه است
import config

config = config.configure()

#مسیر سیستم پیکربندی شده است – اکنون می‌توان عملیات واردسازی را انجام داد
from Enseignant import Enseignant

# یک معلم
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "", "âge": 56})
enseignant1.show()
  • خط ۱۰: یک معلم با نام کوچک خالی ایجاد می‌شود. این باید یک خطا ایجاد کند زیرا کلاس [Personne] نام‌های کوچک خالی را قبول نمی‌کند. این مثال تفاوت بین یک دیکشنری و یک شیء را نشان می‌دهد. شیء دوم می‌تواند اعتبار ویژگی‌های خود را بررسی کند، در حالی که دیکشنری نمی‌تواند؛

نتایج به شرح زیر است:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/fromdict_02.py
Traceback (most recent call last):
  File "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/fromdict_02.py", line 10, in <module>
    enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "", "âge": 56})
  File "C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\classes\02/entities\BaseEntity.py", line 55, in fromdict
    setattr(self, key, value)
  File "C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\classes\02/entities\Personne.py", line 42, in prénom
    raise MyException(11, "Le prénom doit être une chaîne de caractères non vide")
MyException.MyException: MyException[11, Le prénom doit être une chaîne de caractères non vide]

Process finished with exit code 1
13.2.1.2.7. اسکریپت [fromdict_03]

اسکریپت [fromdict_03] به شرح زیر است:


# در حال پیکربندی برنامه است
import config

config = config.configure()

#مسیر سیستم پیکربندی شده است – اکنون می‌توان عملیات واردسازی را انجام داد
from Enseignant import Enseignant

# یک معلم
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "albert", "âge": 56, "sexe": "M"})
enseignant1.show()
  • خط ۱۰: یک معلم از دیکشنری‌ای ایجاد می‌شود که شامل کلیدی (gender) است که به کلاس [Enseignant] تعلق ندارد. بنابراین باید یک استثنا پرتاب شود؛

نتایج به شرح زیر است:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/fromdict_03.py
Traceback (most recent call last):
  File "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/fromdict_03.py", line 10, in <module>
    enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "albert", "âge": 56, "sexe": "M"})
  File "C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\classes\02/entities\BaseEntity.py", line 51, in fromdict
    raise MyException(2, f"la clé [{key}] n'est pas autorisée")
MyException.MyException: MyException[2, la clé [sexe] n'est pas autorisée]

Process finished with exit code 1
13.2.1.2.8. اسکریپت [fromdict_04]

اسکریپت [fromdict_04] کپی [fromdict_03] است با یک تفاوت جزئی:


# اپلیکیشن در حال پیکربندی است
import config

config = config.configure()

#مسیر سیستم پیکربندی شده است – اکنون می‌توان عملیات واردسازی را انجام داد
from Enseignant import Enseignant

# یک معلم
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "albert", "âge": 56, "sexe": "M"}, silent=True)
enseignant1.show()
  • خط ۱۰: پارامتر [silent=True] برای مشخص کردن این مورد استفاده شده است که اگر یک کلید دیکشنری از ویژگی‌های کلاس [Enseignant] نباشد، باید به سادگی نادیده گرفته شود. در این حالت، هیچ استثنایی پرتاب نخواهد شد؛

نتایج به شرح زیر است:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/fromdict_04.py
Enseignant[1, albert, lourou, 56]

Process finished with exit code 0

13.2.2. متد [BaseEntity.asdict]

13.2.2.1. Définition

متد [BaseEntity.asdict] یک دیکشنری بازمی‌گرداند که کلیدهای آن ویژگی‌های شیء هستند:


    def asdict(self, included_keys: list = None, excluded_keys: list =[]) -> dict:
        # ویژگی‌های شیء
        attributes = self.__dict__
        # ویژگی‌های جدید
        new_attributes = {}
        # تکرار ویژگی‌ها
        for key, value in attributes.items():
            # اگر کلید صراحتاً درخواست شده باشد
            if included_keys and key in included_keys:
                self.set_value(key, value, new_attributes)
            #در غیر این صورت، اگر کلید مستثنی نشده باشد
            elif not included_keys and key not in self.__class__.excluded_keys and key not in excluded_keys:
                self.set_value(key, value, new_attributes)
        # واژه‌نامهٔ ویژگی‌ها را بازمی‌گرداند
        return new_attributes

توضیحات

  • خط ۱: تابع [asdict] دیکشنری ویژگی‌های شیء را بازمی‌گرداند؛
  • خط ۱: [included_keys]: فهرست کلیدهایی که باید در دیکشنری گنجانده شوند؛
  • خط ۱: [excluded_keys]: فهرست کلیدهایی که باید از دیکشنری حذف شوند؛
  • خط ۳: ویژگی [self.__dict__] دیکشنری ویژگی‌های شیء را بازمی‌گرداند. نام‌های ویژگی‌ها، کلیدها و مقادیرشان، مقادیر دیکشنری هستند. یک شی ممکن است شامل ارجاع به اشیاء دیگر باشد. در چنین مواردی، نام ویژگی‌ها با نام کلاسی که به آن تعلق دارند، پیش‌وندگذاری می‌شود. این چیزی است که ما نمی‌خواهیم. ما ویژگی‌ها را بدون پیش‌وندشان می‌خواهیم؛
  • خط ۳: باید توجه داشت که اگر تابع [asdict] در داخل کلاسی که از [BaseEntity] ارث‌بری شده است اجرا شود، خاصیت [self.__dict__] دیکشنری ویژگی‌های شیء ارث‌برده را بازمی‌گرداند؛
  • خط ۵: دیکشنری‌ای که باید ساخته شود؛
  • خط ۷: ما بر روی مقادیر [self.__dict__] در قالب (کلید، مقدار) حلقه می‌زنیم؛
  • خط ۹: اگر کلید فعلی در فهرست کلیدهای قابل درج باشد، آنگاه توسط تابع [set_value] به فرهنگ لغت [new_attributes] افزوده می‌شود، که به‌زودی آن را شرح خواهیم داد؛
  • خط ۱۲: اگر پارامتر [included_keys] موجود نباشد، از پارامتر [excluded_keys] استفاده می‌شود. اگر ویژگی در میان ویژگی‌های قابل حذف نباشد، به دیکشنری [new_attributes] اضافه می‌شود؛
  • خط ۱۲: چندین روش برای حذف یک ویژگی از فرهنگ لغت وجود دارد:
    • در سطح ویژگی کلاس [excluded_keys] تعریف شده است؛
    • این مورد در لیست [excluded_keys] که به تابع [asdict] ارسال شده، تعریف شده است؛
    • پارامتر [included_keys] موجود است و شامل ویژگی نمی‌شود؛
  • خط ۱۵: دیکشنری [new_attributes] بازگردانده می‌شود

تابع [set_value] در خطوط ۱۰ و ۱۳ به شرح زیر است:


    @staticmethod
    def set_value(key: str, value, new_attributes: dict):
        #کلیدها ممکن است به شکل __Class__key باشند
        match = re.match("^.*?__(.*?)$", key)
        if match:
            # ما کلید جدید را ثبت می‌کنیم
            newkey = match.groups()[0]
        else:
            #کلید بدون تغییر باقی می‌ماند
            newkey = key
        # کلید جدید را در دیکشنری وارد کنید [new_attributes]
        #تبدیل مقدار مرتبط، در صورت لزوم، به یکی از انواع
        # دیکشنری، لیست، نوع ساده
        new_attributes[newkey] = BaseEntity.check_value(value)

نظرات

  • خط ۴: بررسی می‌کنیم که آیا کلید به شکل __Class_key است. این شکلی است که اگر به یک شیء داخل شیء اصلی تعلق داشته باشد، به خود می‌گیرد. در این صورت، ما فقط می‌خواهیم رشته [key] را نگه داریم؛
  • خط ۷: ما فقط رشته‌ای را که پس از دو کاراکتر زیرخط‌دار آخر قرار دارد، حفظ می‌کنیم؛
  • خطوط ۸–۱۰: اگر کلید در شکل __Class_key نباشد، آنگاه به همان صورت حفظ می‌شود؛
  • خطوط ۱۱–۱۴: مقدار مرتبط با کلید [newkey] با استفاده از متد استاتیک [BaseEntity.check_value] محاسبه می‌شود؛

روش استاتیک [BaseEntity.check_value] به شرح زیر است:


    @staticmethod
    def check_value(value):
        #مقدار ممکن است از نوع BaseEntity، لیست، دیکشنری یا یک نوع ساده باشد
        #آیا مقدار یک نمونه از BaseEntity است؟
        if isinstance(value, BaseEntity):
            value2 = value.asdict()
        #آیا مقدار از نوع 'list' است؟
        elif isinstance(value, list):
            value2 = BaseEntity.list2list(value)
        #آیا مقدار از نوع 'dict' است؟
        elif isinstance(value, dict):
            value2 = BaseEntity.dict2dict(value)
        #مقدار از نوع ساده است
        else:
            value2 = value
        # نتیجه بازگردانده می‌شود
        return value2
  • خط ۱: متد [check_value] ایستا است (متد کلاس، نه متد نمونه). این متد مقدار مرتبط با یک کلید در دیکشنری را به‌عنوان پارامتر می‌پذیرد:
    • خط ۱۷: اگر این مقدار از نوع ساده باشد، بدون تغییر باقی می‌ماند؛
    • خطوط ۵–۶: اگر این مقدار از نوع BaseEntity باشد، مقدار با دیکشنری آن جایگزین می‌شود. این منجر به یک فراخوانی بازگشتی می‌شود؛
    • خطوط ۸–۹: اگر این مقدار یک لیست باشد، با مقدار [BaseEntity.list2list] جایگزین می‌شود؛
    • خطوط ۱۱–۱۲: اگر این مقدار یک دیکشنری باشد، با مقدار [BaseEntity.dict2dict] جایگزین می‌شود؛

متد استاتیک [BaseEntity.list2list] به شرح زیر است:


    @staticmethod
    def list2list(liste: list) -> list:
        # ما عناصر لیست را بررسی می‌کنیم
        newlist = []
        for value in liste:
            newlist.append(BaseEntity.check_value(value))
        # ما لیست جدید را بازمی‌گردانیم
        return newlist
  • خط ۲: متد یک لیست را به‌عنوان پارامتر می‌پذیرد و یک لیست را بازمی‌گرداند؛
  • خطوط ۵–۶: هر مقدار در لیست ورودی با مقدار بازگشتی متد استاتیک [BaseEntity.check_value] جایگزین می‌شود. بنابراین این یک فراخوانی بازگشتی است. متد استاتیک [BaseEntity.check_value] تا زمانی که پارامتر آن [value] یک نوع ساده (نه نوع BaseEntity، لیست یا دیک) باشد، فراخوانی می‌شود؛

متد استاتیک [BaseEntity.dict2dict] به شرح زیر است:


    @staticmethod
    def dict2dict(dictionary: dict) -> dict:
        # ما عناصر فرهنگ لغت را بررسی می‌کنیم
        newdict = {}
        for key, value in dictionary.items():
            newdict[key] = BaseEntity.check_value(value)
        # واژه‌نامهٔ جدید بازگردانده می‌شود
        return newdict
  • خط ۲: متد یک دیکشنری را به‌عنوان پارامتر می‌پذیرد و یک دیکشنری را بازمی‌گرداند؛
  • خطوط ۵–۶: هر مقدار در دیکشنری که به‌عنوان پارامتر ارسال شده است، با مقداری که توسط متد استاتیک [BaseEntity.check_value] بازگردانده می‌شود، جایگزین می‌گردد. بنابراین این یک فراخوانی بازگشتی است. متد استاتیک [BaseEntity.check_value] تا زمانی که پارامتر آن [value] یک نوع ساده (نه نوع BaseEntity، لیست یا dict) باشد، فراخوانی می‌شود؛

13.2.2.2. Exemples

اسکریپت [asdict_01] کاربردهای مختلف متد [asdict] را نشان می‌دهد:


# برنامه پیکربندی شده است
import config
config = config.configure()

#مسیر سیستم پیکربندی شده است – اکنون می‌توانیم واردسازی‌ها را انجام دهیم
from Enseignant import Enseignant
from BaseEntity import BaseEntity

# یک معلم
enseignant1 = Enseignant().fromdict({"id"1"nom""lourou""prénom""paul""âge"56})
dict1 = enseignant1.asdict()
print(type(dict1))
print(enseignant1.__dict__)
print(dict1)
print(enseignant1.asdict(excluded_keys=["_Personne__âge"]))
Enseignant.excluded_keys = ["_Personne__prénom"]
print(enseignant1)
# مدرس دیگری
enseignant2 = Enseignant().fromdict({"id"2"nom""abélard""prénom""béatrice""âge"57})
print(enseignant2.asdict())
print(enseignant2.asdict(included_keys=["_Personne__nom"]))
# فهرستی از موجودیت‌ها در داخل یک موجودیت
Enseignant.excluded_keys = []
entity1 = BaseEntity()
enseignants = [enseignant1, enseignant2]
setattr(entity1, "enseignants", enseignants)
print(entity1.asdict())
#یک فرهنگ لغتِ موجودیت درون یک موجودیت
matières = {"maths": enseignant1, "français": enseignant2}
setattr(entity1, "matières", matières)
print(entity1.asdict())

نتایج اجرای برنامه به شرح زیر است:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/asdict_01.py
<class 'dict'>
{'_BaseEntity__id': 1, '_Personne__nom': 'lourou', '_Personne__prénom': 'paul', '_Personne__âge': 56}
{'id': 1, 'nom': 'lourou', 'prénom': 'paul', 'âge': 56}
{'id': 1, 'nom': 'lourou', 'prénom': 'paul'}
{"id": 1, "nom": "lourou", "âge": 56}
{'id': 2, 'nom': 'abélard', 'âge': 57}
{'nom': 'abélard'}
{'enseignants': [{'id': 1, 'nom': 'lourou', 'prénom': 'paul', 'âge': 56}, {'id': 2, 'nom': 'abélard', 'prénom': 'béatrice', 'âge': 57}]}
{'enseignants': [{'id': 1, 'nom': 'lourou', 'prénom': 'paul', 'âge': 56}, {'id': 2, 'nom': 'abélard', 'prénom': 'béatrice', 'âge': 57}], 'matières': {'maths': {'id': 1, 'nom': 'lourou', 'prénom': 'paul', 'âge': 56}, 'français': {'id': 2, 'nom': 'abélard', 'prénom': 'béatrice', 'âge': 57}}}

Process finished with exit code 0
  • خط ۴ مزیت روش [asdict] را نسبت به استفاده از خاصیت [__dict__] نشان می‌دهد. پیش‌وند کلاس از روی خاصیت‌ها حذف می‌شود. این کار برای نمایش مناسب‌تر است؛
  • چندین روش برای استفاده از متد [asdict] وجود دارد:
    • اگر همه ویژگی‌ها را می‌خواهید، از روش [asdict] بدون هیچ پارامتری استفاده کنید؛
    • اگر فقط برخی از ویژگی‌ها را می‌خواهید:
      • ویژگی‌های قابل‌شامل بیشتر از ویژگی‌های قابل‌حذف هستند: ما فقط از پارامتر [excluded_keys] استفاده خواهیم کرد؛
      • ویژگی‌های کمتری برای درج نسبت به حذف وجود دارد: ما فقط از پارامتر [included_keys] استفاده خواهیم کرد؛

13.2.3. روش [BaseEntity.asjson]

این متد رشته jSON را از یک شیء از نوع [BaseEntity] یا یک نوع مشتق‌شده بازیابی می‌کند. این متد رشته jSON را از فرهنگ لغت بازگردانده‌شده توسط متد [asdict] نمایش می‌دهد. کد آن به شرح زیر است:


def asjson(self, included_keys: list = None, excluded_keys: list = []) -> str:
        # رشته JSON
        return json.dumps(self.asdict(included_keys=included_keys, excluded_keys=excluded_keys), ensure_ascii=False)
  • خط ۱: پارامترهای متد [asjson] همان پارامترهای متد [asdict] هستند؛

در اینجا مثالی (asjson_01) از استفاده این روش آورده شده است:


# برنامه پیکربندی شده است
import config
config = config.configure()

#مسیر سیستم پیکربندی شده است – اکنون می‌توان وارداتی انجام داد
from Enseignant import Enseignant
from BaseEntity import BaseEntity

# یک معلم
enseignant1 = Enseignant().fromdict({"id"1"nom""lourou""prénom""paul""âge"56})
print(type(enseignant1.asjson()))
print(enseignant1.asjson(excluded_keys=["_Personne__âge"]))
Enseignant.excluded_keys = ["_Personne__prénom"]
print(enseignant1.asjson())
# یک معلم دیگر
enseignant2 = Enseignant().fromdict({"id"2"nom""abélard""prénom""béatrice""âge"57})
print(enseignant2.asjson())
print(enseignant2.asjson(included_keys=["_Personne__nom"]))
# فهرستی از موجودیت‌ها در یک موجودیت
Enseignant.excluded_keys = []
entity1 = BaseEntity()
enseignants = [enseignant1, enseignant2]
setattr(entity1, "enseignants", enseignants)
print(entity1.asjson())
# یک فرهنگ لغت از موجودیت‌ها درون یک موجودیت
matières = {"maths": enseignant1, "français": enseignant2}
setattr(entity1, "matières", matières)
print(entity1.asjson())

نتایج به شرح زیر است:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/asjson_01.py
<class 'str'>
{"id": 1, "nom": "lourou", "prénom": "paul"}
{"id": 1, "nom": "lourou", "âge": 56}
{"id": 2, "nom": "abélard", "âge": 57}
{"nom": "abélard"}
{"enseignants": [{"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56}, {"id": 2, "nom": "abélard", "prénom": "béatrice", "âge": 57}]}
{"enseignants": [{"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56}, {"id": 2, "nom": "abélard", "prénom": "béatrice", "âge": 57}], "matières": {"maths": {"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56}, "français": {"id": 2, "nom": "abélard", "prénom": "béatrice", "âge": 57}}}

Process finished with exit code 0

روش [BaseEntity.__str__] از روش [asjson] برای نمایش هویت شیء [BaseEntity] یا اشیاء مشتق آن استفاده می‌کند:


# toString
    def __str__(self) -> str:
        return self.asjson()

13.2.4. روش [BaseEntity.fromjson]

روش [BaseEntity.fromjson] اجازه می‌دهد یک شیء از نوع [BaseEntity] یا یک نوع مشتق‌شده از یک دیکشنری از نوع jSON مقداردهی اولیه شود. کد آن به شرح زیر است:


def fromjson(self, json_state: str, silent: bool = False):
        #وضعیت شیء از رشته jSON به‌روزرسانی می‌شود
        return self.fromdict(json.loads(json_state), silent=silent)
  • خط ۱: این متد دو پارامتر می‌گیرد:
    • [json_state]: دیکشنری jSON که برای مقداردهی اولیه شیء [BaseEntity] استفاده خواهد شد؛
    • [silent]: برای مشخص کردن اینکه آیا وجود یک کلید در دیکشنری jSON که نمی‌تواند به‌عنوان ویژگیِ شیء [BaseEntity] پذیرفته شود، باعث ایجاد یک استثنا (silent=False) می‌شود یا صرفاً نادیده گرفته می‌شود (silent=True
  • خط ۳: ما با ساختن دیکشنری پایتون که نمایانگر دیکشنری jSON است شروع می‌کنیم، سپس از متد [fromdict] برای مقداردهی اولیه شی [BaseEntity] از این دیکشنری پایتون استفاده می‌کنیم؛

در اینجا یک مثال (fromjson_01):


# برنامه پیکربندی شده است
import config

config = config.configure()

#مسیر سیستم پیکربندی شده است – اکنون می‌توان واردات را انجام داد
from Enseignant import Enseignant
import json

# یک معلم
json1 = json.dumps({"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56})
enseignant1 = Enseignant().fromjson(json1)
enseignant1.show()
  • خط ۱۱: رشته jSON از یک دیکشنری ایجاد می‌شود؛
  • خط ۱۲: یک شیء [Enseignant] با این رشته مقداردهی اولیه می‌شود؛
  • خط ۱۳: معلم نمایش داده می‌شود؛

نتایج به شرح زیر است:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/fromjson_01.py
Enseignant[1, paul, lourou, 56]

Process finished with exit code 0

13.2.5. اسکریپت [main]

اسکریپت [main] روش‌های مختلف برخورد شده را خلاصه می‌کند:


# در حال پیکربندی برنامه است
import config

config = config.configure()

#مسیر سیستم پیکربندی شده است – اکنون می‌توان عملیات واردسازی را انجام داد
from BaseEntity import BaseEntity
from MyException import MyException


#یک کلاس
class ChildEntity(BaseEntity):
    #ویژگی‌های مستثنی‌شده از وضعیت کلاس
    excluded_keys = []

    @staticmethod
    def get_allowed_keys():
        return ["att1", "att2", "att3", "att4"]

    @property
    def att1(self) -> int:
        return self.__att1

    @att1.setter
    def att1(self, value: int):
        if 10 >= value >= 1:
            self.__att1 = value
        else:
            raise MyException(1, f"L'attribut [att1] attend une valeur dans l'intervalle [1,10] ({value})")


# پیکربندی ChildEntity
ChildEntity.excluded_keys = []
# مثال ChildEntity
child = ChildEntity().fromdict({"att1": 1, "att2": 2})
# در نام‌های ویژگی دقت کنید
#این‌ها نام‌هایی هستند که در [excluded_keys] و [included_keys] استفاده می‌شوند
print(child.__dict__)
# ویژگی‌هایی که با کلاس خود پیش‌وند نشده‌اند
print(child)

# مثال ChildEntity
try:
    child = ChildEntity().fromdict({"att1": 1, "att5": 5})
    print(child)
except MyException as erreur:
    print(erreur)

# مثال ChildEntity
child = ChildEntity().fromdict({"att1": 1, "att2": 2, "att3": 3, "att4": 4})
print(child)

# استثنا کردن برخی کلیدها از وضعیت نمونه
ChildEntity.excluded_keys = ['att3']
print(child)

#یک کلید صراحتاً از نمایش حذف شده است
# این کلید به فهرست کلیدهای مستثنی‌شده در سطح کلاس اضافه می‌شود
print(child.asdict(excluded_keys=["_ChildEntity__att1"]))
print(child.asjson(excluded_keys=["att2"]))

# رابطه کلاس با دیکشنری
# می‌تواند اعتبار محتوای خود را بررسی کند
try:
    child = ChildEntity().fromdict({"att1": 20})
except MyException as erreur:
    print(erreur)

# مثال ChildEntity
child1 = ChildEntity().fromdict({"att1": 1, "att2": 2, "att3": 3, "att4": 4})
# نمونه ChildEntity حاوی نمونهٔ دیگری از ChildEntity
child2 = ChildEntity().fromdict({"att1": 10, "att2": 20, "att3": 30, "att4": child1})
print(child2)

# included_keys بر excluded_keys اولویت دارد، که سپس نادیده گرفته می‌شوند
ChildEntity.excluded_keys = ['_ChildEntity__att1', 'att2']
print(child.asdict(included_keys=["_ChildEntity__att1", "att3"], excluded_keys=["att3", "att4"]))

نتایج اجرای برنامه به شرح زیر است:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/main.py
{'_ChildEntity__att1': 1, 'att2': 2}
{"att1": 1, "att2": 2}
MyException[2, la clé [att5] n'est pas autorisée]
{"att1": 1, "att2": 2, "att3": 3, "att4": 4}
{"att1": 1, "att2": 2, "att4": 4}
{'att2': 2, 'att4': 4}
{"att1": 1, "att4": 4}
MyException[1, L'attribut [att1] attend une valeur dans l'intervalle [1,10] (20)]
{"att1": 10, "att2": 20, "att4": {"att1": 1, "att2": 2, "att4": 4}}
{'att1': 1, 'att3': 3}

Process finished with exit code 0

به خط ۲ نتایج توجه کنید: این ویژگی [ChildEntity.__dict__] (خط ۳۸ کد) است که به ما امکان می‌دهد نام ویژگی‌هایی را که باید در لیست‌های [included_keys] و [excluded_keys] گنجانده شوند، شناسایی کنیم. توجه شود، مجدداً در خط ۲ نتایج، بسته به اینکه آیا ویژگی از طریق گتر/ستتر درون کلاس تعریف شده باشد یا به همان شیوه یک کلید دیکشنری ایجاد شده باشد، ممکن است با نام کلاس [ChildEntity] پیش‌وند شود یا نشود.