Skip to content

13. الفئتان العامتان [BaseEntity] و [MyException]

سنقوم الآن بتعريف فئتين سنستخدمهما بانتظام في المستقبل.

Image

13.1. فئة MyException

توفر فئة [MyException] (MyException.py) فئة استثناء مخصصة:


# une classe d'exception propriétaire dérivant de [BaseException]
class MyException(BaseException):
    # constructeur
    def __init__(self: object, code: int, message: str):
        # parent
        BaseException.__init__(self, message)
        # code erreur
        self.code = code
 
    # toString
    def __str__(self):
        return f"MyException[{self.code}, {super().__str__()}]"
 
    # getter
    @property
    def code(self) -> int:
        return self.__code
 
    # setter
    @code.setter
    def code(self, code: int):
        # le code d'erreur doit être un entier positif
        if isinstance(code, int) and code > 0:
            self.__code = code
        else:
            # exception
            raise BaseException(f"code erreur {code} incorrect")

ملاحظات

  • السطر 2: فئة [MyException] مشتقة من فئة [BaseException] المحددة مسبقًا؛
  • السطر 4: يقبل المنشئ معلمتين:
    • [code]: رمز خطأ صحيح؛
    • [message]: رسالة خطأ؛
  • السطر 6: يتم تمرير رسالة الخطأ إلى الفئة الأصلية؛
  • الأسطر 14-27: يتم الوصول إلى السمة [code] عبر أداة الحصول/التعيين؛
  • السطران 23-24: يتم التحقق من صحة السمة [code]: يجب أن تكون عددًا صحيحًا أكبر من 0؛

13.2. فئة [BaseEntity]

ستكون فئة [BaseEntity] هي الفئة الأصلية لمعظم الفئات التي ننشئها لتغليف المعلومات حول كائن ما. من الآن فصاعدًا، سنستخدم بشكل أساسي نوعين من الفئات:

  • الفئات التي يكون الغرض الوحيد منها هو تغليف المعلومات حول كائن واحد في مكان واحد. لن يكون لهذه الفئات أي سلوكيات (طرق) بخلاف أدوات الحصول/التعيين ووظيفة العرض (__str__). إذا كان هناك N كائنًا لإدارتها، يتم إنشاء مثيلات لهذه الفئات N مرة. ستكون [BaseEntity] هي الفئة الأم لهذا النوع من الفئات؛
  • الفئات التي يتمثل دورها الأساسي في تغليف الطرق ومعلومات قليلة جدًا. سيتم إنشاء مثيلات لهذه الفئات مرة واحدة فقط (فئة فردية). ويتمثل دورها في تنفيذ خوارزميات التطبيق؛

فئة [BaseEntity] هي كما يلي:

#  imports
import json
import re

from MyException import MyException


class BaseEntity(object):
    #  properties excluded from class state
    excluded_keys = []

    #  class properties
    @staticmethod
    def get_allowed_keys() -> list:
        #  id: object identifier
        return ["id"]

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

    #  getter
    @property
    def id(self) -> int:
        return self.__id

    #   setter
    @id.setter
    def id(self, id):
        #  id must be an integer >=0
        try:
            id = int(id)
            erreur = id < 0
        except:
            erreur = True
        #  mistake?
        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] هو تسهيل عمليات التحويل بين الكائنات والقواميس وبين الكائنات وملفات JSON. وهي توفر الطرق التالية:
    • [asdict]: تُرجع قاموسًا لخصائص الكائن؛
    • [fromdict]: تنشئ كائنًا من قاموس؛
    • [asjson]: تُرجع سلسلة JSON للكائن، على غرار الدالة [__str__]؛
    • [fromjson]: تنشئ كائنًا من سلسلة JSON الخاصة به؛
  • فئة [BaseEntity] مصممة لتكون مشتقة وليست للاستخدام كما هي؛
  • الأسطر 22–25: تحتوي فئة [BaseEntity] على خاصية واحدة فقط، وهي العدد الصحيح [id]. هذه الخاصية هي معرف الكائن. في الممارسة العملية، غالبًا ما يكون من المفيد التمييز بين مثيلات الفئة نفسها. سنقوم بذلك باستخدام هذه الخاصية، التي تكون فريدة لكل مثيل. علاوة على ذلك، غالبًا ما تأتي الكائنات من قواعد البيانات حيث يتم تحديدها بواسطة مفتاح أساسي، وعادةً ما يكون عددًا صحيحًا. في مثل هذه الحالات، ستعمل [id] كمفتاح أساسي؛
  • الأسطر 27–40: مُعيّن الخاصية [id]. نتحقق من أنها عدد صحيح >= 0. إذا لم يكن الأمر كذلك، يتم إلقاء استثناء من النوع [MyException] (السطر 39)؛
  • السطر 10: [excluded_keys] هي سمة فئة، وليست سمة مثيل. لذلك، نكتب [BaseEntity.excluded_keys]. هذه السمة هي قائمة تحتوي على خصائص الفئة التي لا تشارك في تحويلات Object/Dictionary و Object/JSON؛
  • الأسطر 12–16: [get_allowed_keys] تُرجع قائمة بخصائص الفئة. في تحويل Dictionary → Object أو JSON → Object، لن يتم قبول سوى المفاتيح الموجودة في هذه القائمة. ستحتاج كل فئة مشتقة من فئة [BaseEntity] إلى إعادة تعريف هذه القائمة؛

من المهم أن نفهم هنا أن خصائص ووظائف فئة [BaseEntity] متاحة للفئات المشتقة من [BaseEntity]. هذه هي النقطة الأساسية التي يجب فهمها.

سنقوم الآن بفحص كود فئة [BaseEntity] بالتفصيل. إنه متقدم جدًا. يمكن للقراء المبتدئين قراءة وصف دور كل دالة دون الخوض في الكود نفسه.

13.2.1. طريقة [BaseEntity.fromdict]

13.2.1.1. التعريف

تسمح لك طريقة [fromdict] بتهيئة كائن [BaseEntity] أو كائن مشتق من قاموس:

def fromdict(self, state: dict, silent=False):
        #  object is updated
        #  authorized keys
        allowed_keys = self.__class__.get_allowed_keys()
        #  state key traversal
        for key, value in state.items():
            #  is the key authorized?
            if key not in allowed_keys:
                if not silent:
                    raise MyException(2, f"la clé {key} n'est pas autorisée")
            else:
                #  we try to assign the value to the key
                #  we let any exception go up
                setattr(self, key, value)
        #  we return the object
        return self

تعليقات

  • السطر 1: تستقبل الدالة قاموس [state] كمعلمة، والذي سيتم من خلاله تهيئة الكائن الحالي؛
  • السطر 4: نستدعي الدالة الثابتة [get_allowed_keys] للفئة التي استدعت الدالة [fromdict]. إذا كنا نتعامل مع فئة مشتقة من [BaseEntity] وقامت تلك الفئة المشتقة بإعادة تعريف الدالة الثابتة [get_allowed_keys] في [ ]، فإن الدالة [get_allowed_keys] هي التي يتم استدعاؤها. تعيد كل فئة مشتقة تعريف هذه الدالة الثابتة لإعلان خصائصها؛
  • السطر 6: نقوم بالتكرار عبر مفاتيح وقيم قاموس [state]؛
  • السطر 8: إذا لم يكن المفتاح [key] أحد خصائص الفئة، فإما:
    • يتم تجاهله؛
    • يتم إلقاء استثناء (السطر 10). يحدد المطور تفضيلاته عن طريق تمرير المعلمة [silent] المناسبة (السطر 1). تؤدي القيمة الافتراضية لـ [silent] إلى إلقاء استثناء في حالة محاولة تهيئة الكائن بخاصية لا يمتلكها؛
  • السطر 14: إذا كان المفتاح من بين خصائص الكائن، يتم تعيينه إلى الكائن [self] باستخدام الدالة المحددة مسبقًا [setattr]؛
  • السطر 16: تُرجع الدالة الكائن الذي تمت تهيئته؛

13.2.1.2. أمثلة

Image

13.2.1.2.1. فئة [Utils]

فئة [Utils] (Utils.py) هي كما يلي:

class Utils:
    #  static method
    @staticmethod
    def is_string_ok(string: str) -> bool:
        #  string is a string
        erreur = not isinstance(string, str)
        if not erreur:
            #  is the chain empty?
            erreur = string.strip() == ''
        #  result
        return not erreur

يحدد، في الأسطر 3–11، طريقة ثابتة تُرجع قيمة منطقية true إذا كانت معلمتها [str] عبارة عن سلسلة غير فارغة؛

13.2.1.2.2. فئة [Person]

تشتق فئة [Person] (Person.py) من فئة [BaseEntity]:

#  imports
from BaseEntity import BaseEntity
from MyException import MyException
from Utils import Utils


#  person class
class Personne(BaseEntity):
    #  properties excluded from class state
    excluded_keys = []

    #  class properties
    #  id: person's identifier
    #  first name: person's first name
    #  name: person's name
    #  age: age of the person
    @staticmethod
    def get_allowed_keys() -> list:
        #  id: object identifier
        return BaseEntity.get_allowed_keys() + ["nom", "prénom", "âge"]

    #  getters
    @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

    #  setters
    @prénom.setter
    def prénom(self, prénom: str):
        #  first name must be non-empty
        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):
        #  first name must be non-empty
        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):
        #  age must be an integer >=0
        erreur = False
        if isinstance(âge, int):
            if âge >= 0:
                self.__âge = âge
            else:
                erreur = True
        else:
            erreur = True
        #  mistake?
        if erreur:
            raise MyException(13, "L'âge doit être un entier >=0")
  • السطر 8: فئة [Person] مشتقة من فئة [BaseEntity]؛
  • الأسطر 8–65: احتفظنا بمعظم فئة [Person] التي صادفناها سابقًا. الاختلافات هي كما يلي:
    • لم تعد الفئة تحتوي على منشئ؛
    • تستخدم الفئة استثناء [MyException]، على سبيل المثال، السطر 65؛
    • تحتوي على طريقة ثابتة، [get_allowed_keys]، الأسطر 17–20، والتي تحدد قائمة خصائصها. تضاف الخصائص الخاصة بفئة [Person] إلى خصائص الفئة الأصلية [BaseEntity]؛
    • تحتوي على قائمة ثابتة [excluded_keysوالتي سنعود إليها لاحقًا؛
13.2.1.2.3. فئة [Teacher]

تشتق فئة [Teacher] (Teacher.py) من فئة [Person]:

#  imports
from MyException import MyException
from Personne import Personne
from Utils import Utils


#  class Teacher
class Enseignant(Personne):
    #  properties excluded from class state
    excluded_keys = []

    #  class properties
    #  id: person's identifier
    #  first name: person's first name
    #  name: person's name
    #  age: age of the person
    #  discipline: discipline taught
    @staticmethod
    def get_allowed_keys() -> list:
        #  id: object identifier
        return Personne.get_allowed_keys() + ["discipline"]

    #  properties
    @property
    def discipline(self) -> str:
        return self.__discipline

    @discipline.setter
    def discipline(self, discipline: str):
        #  the discipline must be a non-empty string
        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")

    #  show method
    def show(self):
        print(f"Enseignant[{self.id}, {self.prénom}, {self.nom}, {self.âge}]")
  • السطر 8: تمتد فئة [Teacher] (أو تنبثق من) فئة [Person]؛
  • الأسطر 18–21: تحدد قائمة خصائص الفئة؛
  • السطور 37–38: تعرض طريقة [show] هوية المعلم؛
13.2.1.2.4. تكوين [config]

تستخدم البرامج النصية النموذجية التكوين [config] التالي:

def configure():
    import os

    #  configuration file folder
    script_dir = os.path.dirname(os.path.abspath(__file__))

    #  absolute paths of folders to put in the syspath
    absolute_dependencies = [
        #  the BaseEntity class
        f"{script_dir}/entities",
    ]

    #  update syspath
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    #  we return the configuration
    return {}
  • الأسطر 8–10: الدلائل التي تحتوي على تبعيات المشروع؛
  • الأسطر 14–15: يتم إنشاء مسار Python؛
  • السطر 18: إرجاع قاموس فارغ (لا توجد تكوينات أخرى بخلاف syspath
13.2.1.2.5. البرنامج النصي [fromdict_01]

النص البرمجي [fromdict_01] هو كما يلي:

#  configure the application
import config

config = config.configure()

#  syspath is configured - imports can be made
from Enseignant import Enseignant

#  a teacher
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56})
enseignant1.show()
  • السطر 10: نقوم بإنشاء كائن [Teacher] من قاموس. للقيام بذلك، نستخدم المنشئ الافتراضي للفئة لإنشاء كائن [Teacher] نطبق عليه طريقة [fromdict]. من المهم أن نفهم أن طريقة [fromdict] التي يتم تنفيذها هنا هي طريقة الفئة الأم [BaseEntity]. في الواقع:
    • يتم البحث أولاً عن طريقة [fromdict] في فئة [Teacher]. وهي غير موجودة؛
    • ثم يتم البحث عنها في الفئة الأصلية [Person]. وهي غير موجودة؛
    • ثم يتم البحث عنها في الفئة الأصلية [BaseEntity]. وهي موجودة؛
  • السطر 11: يتم عرض الكائن [Teacher]؛

والنتائج هي كما يلي:


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] هو كما يلي:

#  configure the application
import config

config = config.configure()

#  syspath is configured - imports can be made
from Enseignant import Enseignant

#  a teacher
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "", "âge": 56})
enseignant1.show()
  • السطر 10: نقوم بإنشاء معلم باسم أول فارغ. من المفترض أن يؤدي ذلك إلى ظهور استثناء لأن فئة [Person] لا تقبل أسماء أولى فارغة. يوضح هذا المثال الفرق بين القاموس والكائن. يمكن للكائن التحقق من صحة خصائصه، بينما لا يستطيع القاموس ذلك؛

والنتائج هي كما يلي:


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] هو كما يلي:

#  configure the application
import config

config = config.configure()

#  syspath is configured - imports can be made
from Enseignant import Enseignant

#  a teacher
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "albert", "âge": 56, "sexe": "M"})
enseignant1.show()
  • السطر 10: نقوم بإنشاء معلم من قاموس يحتوي على مفتاح (الجنس) لا ينتمي إلى فئة [Teacher]. ومن المفترض أن يتم إثارة استثناء؛

والنتائج هي كما يلي:


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] مع اختلاف بسيط واحد:

#  configure the application
import config

config = config.configure()

#  syspath is configured - imports can be made
from Enseignant import Enseignant

#  a teacher
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "albert", "âge": 56, "sexe": "M"}, silent=True)
enseignant1.show()
  • السطر 10: استخدمنا المعلمة [silent=True] للإشارة إلى أنه إذا لم يكن مفتاح القاموس خاصية من خصائص فئة [Teacher]، فيجب تجاهله ببساطة. في هذه الحالة، لن يتم إثارة أي استثناء؛

والنتائج هي كما يلي:


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. التعريف

تُرجع الطريقة [BaseEntity.asdict] قاموسًا تكون مفاتيحه هي خصائص الكائن:


    def asdict(self, included_keys: list = None, excluded_keys: list =[]) -> dict:
        # attributs de l'objet
        attributes = self.__dict__
        # les nouveaux attributs
        new_attributes = {}
        # on parcourt les attributs
        for key, value in attributes.items():
            # si la clé est explicitement demandée
            if included_keys and key in included_keys:
                self.set_value(key, value, new_attributes)
            # sinon, si la clé n'est pas exclue
            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)
        # on rend le dictionnaire des attributs
        return new_attributes

تعليقات

  • السطر 1: تُرجع الدالة [asdict] قاموس خصائص الكائن؛
  • السطر 1: [included_keys]: قائمة المفاتيح المراد تضمينها في القاموس؛
  • السطر 1: [excluded_keys]: قائمة المفاتيح التي سيتم استبعادها من القاموس؛
  • السطر 3: تعيد الخاصية [self.__dict__] قاموس خصائص الكائن. أسماء الخصائص هي المفاتيح وقيمها هي قيم القاموس. قد يحتوي الكائن على مراجع إلى كائنات أخرى. في هذه الحالة، تُسبق أسماء الخصائص باسم الفئة التي تنتمي إليها. وهذا شيء لا نريده. نريد الخصائص بدون البادئة؛
  • السطر 3: من المهم أن نفهم هنا أنه إذا تم تنفيذ الدالة [asdict] داخل فئة مشتقة من [BaseEntity]، فإن الخاصية [self.__dict__] تُرجع قاموس خصائص الكائن المشتق؛
  • السطر 5: القاموس الذي سنقوم بإنشائه؛
  • السطر 7: نكرر قيم [self.__dict__] في صيغة (المفتاح، القيمة)؛
  • السطر 9: إذا كان المفتاح الحالي ينتمي إلى قائمة المفاتيح المراد تضمينها، فإنه يُضاف إلى قاموس [new_attributes] باستخدام الدالة [set_value]، التي سنصفها بعد قليل؛
  • السطر 12: إذا لم يكن المعامل [included_keys] موجودًا، يتم استخدام المعامل [excluded_keys]. إذا لم تكن الخاصية من بين الخصائص المراد استبعادها، يتم إضافتها إلى قاموس [new_attributes]؛
  • السطر 12: هناك عدة طرق لاستبعاد خاصية من القاموس:
    • تم تعريفها على مستوى سمة الفئة [excluded_keys]؛
    • تم تعريفها في قائمة [excluded_keys] التي تم تمريرها إلى دالة [asdict]؛
    • وجود المعلمة [included_keys] وعدم تضمينها للخاصية؛
  • السطر 15: نُرجع القاموس [new_attributes]

تكون دالة [set_value] الواردة في السطرين 10 و13 كما يلي:

    @staticmethod
    def set_value(key: str, value, new_attributes: dict):
        #  keys can be of the form __Class__key
        match = re.match("^.*?__(.*?)$", key)
        if match:
            #  note the new key
            newkey = match.groups()[0]
        else:
            #  the key remains unchanged
            newkey = key
        #  insert the new key into the [new_attributes] dictionary
        #  type, transforming if necessary the associated value into one of the
        #  dict, list, simple type
        new_attributes[newkey] = BaseEntity.check_value(value)

تعليقات

  • السطر 4: تحقق مما إذا كان المفتاح في صيغة __Class_key. هذه هي الصيغة التي يتخذها إذا كان ينتمي إلى كائن مضمن في الكائن الرئيسي. في هذه الحالة، نريد الاحتفاظ فقط بالسلسلة [key]؛
  • السطر 7: نحتفظ فقط بالسلسلة التي تلي آخر حرفين تحت خط من السلسلة؛
  • الأسطر 8-10: إذا لم يكن المفتاح في صيغة __Class_key، فإننا نحتفظ به كما هو؛
  • الأسطر 11-14: يتم حساب القيمة المرتبطة بالمفتاح [newkey] بواسطة الطريقة الثابتة [BaseEntity.check_value]؛

الطريقة الثابتة [BaseEntity.check_value] هي كما يلي:

    @staticmethod
    def check_value(value):
        #  the value can be of type BaseEntity, list, dict or a simple type
        #  is value an instance of BaseEntity?
        if isinstance(value, BaseEntity):
            value2 = value.asdict()
        #  is value of type list
        elif isinstance(value, list):
            value2 = BaseEntity.list2list(value)
        #  is value a dict type?
        elif isinstance(value, dict):
            value2 = BaseEntity.dict2dict(value)
        #  value is a simple type
        else:
            value2 = value
        #  we return the result
        return value2
  • السطر 1: طريقة [check_value] هي طريقة ثابتة (طريقة فئة، وليست طريقة مثيل). تأخذ كمعلمة القيمة المراد ربطها بمفتاح القاموس:
    • السطر 17: إذا كانت هذه القيمة من النوع البسيط، فإنها تظل دون تغيير؛
    • السطران 5-6: إذا كانت هذه القيمة من نوع BaseEntity، يتم استبدال القيمة بقاموسها. ينتج عن ذلك استدعاء متكرر؛
    • السطران 8-9: إذا كانت هذه القيمة قائمة، يتم استبدالها بالقيمة [BaseEntity.list2list]؛
    • السطران 11-12: إذا كانت هذه القيمة عبارة عن قاموس، يتم استبدالها بالقيمة [BaseEntity.dict2dict]؛

الطريقة الثابتة [BaseEntity.list2list] هي كما يلي:

1
2
3
4
5
6
7
8
    @staticmethod
    def list2list(liste: list) -> list:
        #  inspect list items
        newlist = []
        for value in liste:
            newlist.append(BaseEntity.check_value(value))
        #  return the new list
        return newlist
  • السطر 2: تستقبل الطريقة قائمة وتُرجع قائمة؛
  • السطران 5-6: يتم استبدال كل قيمة في القائمة التي تم تمريرها كمعلمة بالقيمة التي تعيدها الطريقة الثابتة [BaseEntity.check_value]. وبالتالي، فإن هذا استدعاء متكرر. يتم استدعاء الطريقة الثابتة [BaseEntity.check_value] حتى تصبح معلمتها [value] من النوع البسيط (وليس من نوع BaseEntity أو قائمة أو قاموس)؛

الطريقة الثابتة [BaseEntity.dict2dict] هي كما يلي:

1
2
3
4
5
6
7
8
    @staticmethod
    def dict2dict(dictionary: dict) -> dict:
        #  inspect dictionary items
        newdict = {}
        for key, value in dictionary.items():
            newdict[key] = BaseEntity.check_value(value)
        #  the new dictionary is returned
        return newdict
  • السطر 2: تستقبل الطريقة قاموسًا وتُرجع قاموسًا؛
  • السطران 5-6: يتم استبدال كل قيمة في القاموس الذي تم تمريره كمعلمة بالقيمة التي تعيدها الطريقة الثابتة [BaseEntity.check_value]. وبالتالي، فإن هذا استدعاء متكرر. يتم استدعاء الطريقة الثابتة [BaseEntity.check_value] حتى تصبح معلمتها [value] من النوع البسيط (وليس BaseEntity أو قائمة أو قاموس)؛

13.2.2.2. أمثلة

يوضح البرنامج النصي [asdict_01] الاستخدامات المختلفة للطريقة [asdict]:

#  configure the application
import config
config = config.configure()

#  syspath is configured - imports can be made
from Enseignant import Enseignant
from BaseEntity import BaseEntity

#  a teacher
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)
#  another teacher
enseignant2 = Enseignant().fromdict({"id": 2, "nom": "abélard", "prénom": "béatrice", "âge": 57})
print(enseignant2.asdict())
print(enseignant2.asdict(included_keys=["_Personne__nom"]))
#  a list of entities within an entity
Enseignant.excluded_keys = []
entity1 = BaseEntity()
enseignants = [enseignant1, enseignant2]
setattr(entity1, "enseignants", enseignants)
print(entity1.asdict())
#  a dictionary of entities within an entity
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
  • يوضح السطر 4 ميزة طريقة [asdict] مقارنة باستخدام الخاصية [__dict__]. يتم تجريد الخصائص من بادئة الفئة الخاصة بها. وهذا يجعل عرضها أسهل؛
  • هناك عدة طرق لاستخدام طريقة [asdict]:
    • إذا كنت تريد جميع الخصائص: استخدم طريقة [asdict] بدون معلمات؛
    • إذا كنت تريد خصائص معينة فقط:
      • إذا كان هناك عدد أكبر من الخصائص المطلوب تضمينها مقارنةً بالخصائص المطلوب استبعادها: استخدم المعلمة الفردية [excluded_keys]؛
      • إذا كان عدد الخصائص المطلوب تضمينها أقل من عدد الخصائص المطلوب استبعادها: فسنستخدم المعلمة [included_keys] فقط؛

13.2.3. طريقة [BaseEntity.asjson]

تُرجع هذه الطريقة سلسلة JSON لكائن [BaseEntity] أو الفئات المشتقة منه. وتُخرج سلسلة JSON الخاصة بالقاموس الذي تُرجعه الطريقة [asdict]. وفيما يلي نص الكود:

1
2
3
def asjson(self, included_keys: list = None, excluded_keys: list = []) -> str:
        #  the json string
        return json.dumps(self.asdict(included_keys=included_keys, excluded_keys=excluded_keys), ensure_ascii=False)
  • السطر 1: معلمات طريقة [asjson] هي معلمات طريقة [asdict]؛

فيما يلي مثال (asjson_01) يستخدم هذه الطريقة:

#  configure the application
import config
config = config.configure()

#  syspath is configured - imports can be made
from Enseignant import Enseignant
from BaseEntity import BaseEntity

#  a teacher
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())
#  another teacher
enseignant2 = Enseignant().fromdict({"id": 2, "nom": "abélard", "prénom": "béatrice", "âge": 57})
print(enseignant2.asjson())
print(enseignant2.asjson(included_keys=["_Personne__nom"]))
#  a list of entities within an entity
Enseignant.excluded_keys = []
entity1 = BaseEntity()
enseignants = [enseignant1, enseignant2]
setattr(entity1, "enseignants", enseignants)
print(entity1.asjson())
#  a dictionary of entities within an entity
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. وفيما يلي شفرة هذه الطريقة:

1
2
3
def fromjson(self, json_state: str, silent: bool = False):
        #  update object status from jSON string
        return self.fromdict(json.loads(json_state), silent=silent)
  • السطر 1: تأخذ هذه الطريقة معلمتين:
    • [json_state]: قاموس JSON المستخدم لتهيئة كائن [BaseEntity]؛
    • [silent]: للإشارة إلى ما إذا كان وجود مفتاح في قاموس JSON لا يمكن قبوله كخاصية لكائن [BaseEntity] يؤدي إلى حدوث استثناء (silent=False) أو يتم تجاهله ببساطة (silent=True
  • السطر 3: نبدأ بإنشاء قاموس Python الذي يمثل قاموس JSON، ثم نستخدم طريقة [fromdict] لتهيئة كائن [BaseEntity] من قاموس Python هذا؛

فيما يلي مثال (fromjson_01):

#  configure the application
import config

config = config.configure()

#  syspath is configured - imports can be made
from Enseignant import Enseignant
import json

#  a teacher
json1 = json.dumps({"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56})
enseignant1 = Enseignant().fromjson(json1)
enseignant1.show()
  • السطر 11: نقوم بإنشاء سلسلة JSON لقاموس؛
  • السطر 12: يتم تهيئة كائن [Enseignant] باستخدام هذه السلسلة؛
  • السطر 13: يتم عرض المعلم؛

والنتائج هي كما يلي:


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] الطرق المختلفة التي تمت مناقشتها:

#  configure the application
import config

config = config.configure()

#  syspath is configured - imports can be made
from BaseEntity import BaseEntity
from MyException import MyException


#  a class
class ChildEntity(BaseEntity):
    #  attributes excluded from class state
    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})")


#  configuration ChildEntity
ChildEntity.excluded_keys = []
#  instance ChildEntity
child = ChildEntity().fromdict({"att1": 1, "att2": 2})
#  pay attention to property names
#  these are the names used in [excluded_keys] and [included_keys]
print(child.__dict__)
#  properties not prefixed by their class
print(child)

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

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

#  exclusion of certain keys from instance status
ChildEntity.excluded_keys = ['att3']
print(child)

#  a key is explicitly excluded from the display
#  it is added to those excluded globally at class level
print(child.asdict(excluded_keys=["_ChildEntity__att1"]))
print(child.asjson(excluded_keys=["att2"]))

#  class interest in the dictionary
#  it can check the validity of its contents
try:
    child = ChildEntity().fromdict({"att1": 20})
except MyException as erreur:
    print(erreur)

#  instance ChildEntity
child1 = ChildEntity().fromdict({"att1": 1, "att2": 2, "att3": 3, "att4": 4})
#  instance ChildEntity containing another instance ChildEntity
child2 = ChildEntity().fromdict({"att1": 10, "att2": 20, "att3": 30, "att4": child1})
print(child2)

#  included_keys has priority over excluded_keys which are then ignored
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

لاحظ السطر 2 من النتائج: إنها الخاصية [ChildEntity.__dict__] (السطر 38 من الكود) التي تسمح لنا بتحديد أسماء الخصائص التي سيتم تضمينها في قوائم [included_keys] و [excluded_keys]. لاحظ، في السطر 2 من النتائج أيضًا، أنه اعتمادًا على ما إذا كانت الخاصية محددة داخل الفئة عبر getter/setter أو تم إنشاؤها كما يتم إنشاء مفتاح قاموس، فقد يتم أو لا يتم إرفاق اسم الفئة [ChildEntity] بها.