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

    # сеттер
    @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")

Примітки

  • рядок 2: клас [MyException] походить від попередньо визначеного класу [BaseException];
  • рядок 4: конструктор приймає два параметри:
    • [code] — цілочисельний код помилки;
    • [message]: повідомлення про помилку;
  • рядок 6: передаємо повідомлення про помилку до батьківського класу;
  • рядки 14–27: атрибут [code] обробляється за допомогою методів getter/setter;
  • рядки 23–24: перевіряється правильність значення атрибута [code]: воно має бути цілим числом > 0;

13.2. Клас [BaseEntity]

Клас [BaseEntity] буде батьківським класом для більшості класів, які ми створимо для інкапсуляції інформації про об’єкт. Далі ми будемо використовувати переважно два типи класів:

  • класи, єдиною метою яких є інкапсуляція в одному місці інформації про один і той самий об’єкт. Ці класи не матимуть інших методів, окрім геттерів/сеттерів та функції відображення (__str__). Якщо потрібно обробляти N об’єктів, ці класи інстанціюються N разів. [BaseEntity] буде батьківським класом для цього типу класів;
  • класів, основною роллю яких є інкапсуляція методів і дуже невеликого обсягу інформації. Ці класи будуть інстанційовані лише один раз (синглтон). Їхня роль полягає в реалізації алгоритмів додатка;

Клас [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

    # сеттер
    @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] — спростити перетворення «Об’єкт / Словник» та «Об’єкт / 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]. Цей атрибут класу — це список, що містить властивості класу, які не беруть участі в перетвореннях «Об’єкт» / «Словник» та «Об’єкт» / jSON;
  • рядки 12–16: [get_allowed_keys] повертає список властивостей класу. Під час перетворення «Словник» → «Об’єкт» або jSON → «Об’єкт» будуть прийняті лише ті ключі, які містяться в цьому списку. Кожен клас, що походить від класу [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

Коментарі

  • рядок 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. 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

У рядках 3–11 він визначає статичний метод, який повертає логічне значення «true», якщо його параметр [str] є непорожнім рядком;

13.2.1.2.2. Клас [Personne]

Клас [Personne] (Personne.py) походить від класу [BaseEntity]:


# імпорти
from BaseEntity import BaseEntity
from MyException import MyException
from Utils import Utils


# клас «Людина»
class Personne(BaseEntity):
    # властивості, виключені зі стану класу
    excluded_keys = []

    # властивості класу
    # id: ідентифікатор особи
    # ім’я: ім’я особи
    # прізвище: прізвище особи
    # вік: вік особи
    @staticmethod
    def get_allowed_keys() -> list:
        # id: ідентифікатор об’єкта
        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

    # сетери
    @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")
  • рядок 8: клас [Personne] походить від класу [BaseEntity];
  • рядки 8–65: збережено основну частину класу [Personne], який ми вже розглядали. Відмінності такі:
    • клас більше не має конструктора;
    • клас використовує виняток [MyException], приклад — рядок 65;
    • вона має статичний метод [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")

    # метод show
    def show(self):
        print(f"Enseignant[{self.id}, {self.prénom}, {self.nom}, {self.âge}]")
  • рядок 8: клас [Enseignant] розширює (або походить від) класу [Personne];
  • рядки 18–21: визначають перелік властивостей класу;
  • рядки 37–38: метод [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 {}
  • рядки 8–10: папки, що містять залежності проекту;
  • рядки 14–15: формується Python Path;
  • рядок 18: повертається порожній словник (немає інших конфігурацій, окрім syspath);
13.2.1.2.5. Скрипт [fromdict_01]

Скрипт [fromdict_01] має такий вигляд:


# налаштовуємо додаток
import config

config = config.configure()

# syspath налаштовано — можна виконувати імпорт
from Enseignant import Enseignant

# викладач
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56})
enseignant1.show()
  • рядок 10: створюється об’єкт [Enseignant] на основі словника. Для цього використовується конструктор за замовчуванням класу для створення об’єкта [Enseignant], до якого застосовується метод [fromdict]. Слід розуміти, що тут виконується метод [fromdict] з батьківського класу [BaseEntity]. Адже:
    • спочатку метод [fromdict] шукається у класі [Enseignant]. Його там немає;
    • потім його шукають у батьківському класі [Personne]. Його там немає;
    • потім його шукають у батьківському класі [BaseEntity]. Він існує;
  • рядок 11: виводиться об’єкт [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()

# syspath налаштовано — можна виконувати імпорт
from Enseignant import Enseignant

# викладач
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "", "âge": 56})
enseignant1.show()
  • рядок 10: створюється викладач із порожнім ім’ям. Це має спричинити виняток, оскільки клас [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()

# syspath налаштовано — можна виконувати імпорт
from Enseignant import Enseignant

# викладач
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "albert", "âge": 56, "sexe": "M"})
enseignant1.show()
  • рядок 10: створюється об’єкт «викладач» на основі словника, що містить ключ (стать), який не належить до класу [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()

# syspath налаштовано — можна виконувати імпорт
from Enseignant import Enseignant

# викладач
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "albert", "âge": 56, "sexe": "M"}, silent=True)
enseignant1.show()
  • рядок 10: було використано параметр [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

Коментарі

  • рядок 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):
        # ключі можуть мати вигляд __Class__key
        match = re.match("^.*?__(.*?)$", key)
        if match:
            # записуємо новий ключ
            newkey = match.groups()[0]
        else:
            # ключ залишається без змін
            newkey = key
        # вносимо новий ключ до словника [new_attributes]
        # при необхідності перетворюючи пов'язане значення в один із типів
        # dict, list, простий тип
        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):
        # значення може бути типу 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
  • рядок 1: метод [check_value] є статичним (метод класу, а не екземпляра). Він отримує як параметр значення, яке потрібно пов’язати з ключем словника:
    • рядок 17: якщо це значення є простим типом, воно залишається незмінним;
    • рядки 5–6: якщо це значення має тип BaseEntity, воно замінюється на свій словник. У цьому випадку відбувається рекурсивний виклик;
    • рядки 8–9: якщо це значення є списком, то воно замінюється на значення [BaseEntity.list2list];
    • рядки 11–12: якщо це значення є словником, то воно замінюється на значення [BaseEntity.dict2dict];

Статичний метод [BaseEntity.list2list] виглядає так:


    @staticmethod
    def list2list(liste: list) -> list:
        # перевіряємо елементи списку
        newlist = []
        for value in liste:
            newlist.append(BaseEntity.check_value(value))
        # повертаємо новий список
        return newlist
  • рядок 2: метод отримує список і повертає список;
  • рядки 5–6: кожне значення списку, отриманого як параметр, замінюється значенням, повернутим статичним методом [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
  • рядок 2: метод отримує словник і повертає словник;
  • рядки 5–6: кожне значення словника, отриманого як параметр, замінюється значенням, повернутим статичним методом [BaseEntity.check_value]. Отже, маємо рекурсивний виклик. Статичний метод [BaseEntity.check_value] викликається доти, доки його параметр [value] не стане простим типом (а не типом BaseEntity, списком або словником);

13.2.2.2. Exemples

Скрипт [asdict_01] демонструє різні варіанти використання методу [asdict]:


# налаштовуємо додаток
import config
config = config.configure()

# syspath налаштовано — можна виконувати імпорт
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
  • у рядку 4 показано переваги методу [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)
  • рядок 1: параметри методу [asjson] є параметрами методу [asdict];

Ось приклад (asjson_01), у якому використовується цей метод:


# налаштовуємо додаток
import config
config = config.configure()

# syspath налаштовано — можна виконувати імпорт
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)
  • рядок 1: метод приймає два параметри:
    • [json_state]: словник jSON, який буде використовуватися для ініціалізації об’єкта [BaseEntity];
    • [silent]: щоб вказати, чи наявність у словнику jSON ключа, який не може бути прийнятий як властивість об’єкта [BaseEntity], спричиняє виняток (silent=False) чи просто ігнорується (silent=True);
  • рядок 3: спочатку створюється словник Python, що відображає словник jSON, а потім за допомогою методу [fromdict] ініціалізується об’єкт [BaseEntity] на основі цього словника Python;

Ось приклад (fromjson_01):


# налаштовується додаток
import config

config = config.configure()

# syspath налаштовано — можна виконувати імпорт
from Enseignant import Enseignant
import json

# викладач
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] підсумовує різні методи, що зустрічаються:


# налаштовуємо додаток
import config

config = config.configure()

# syspath налаштовано — можна виконувати імпорт
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

Звернемо увагу на 2-й рядок результатів: саме властивість [ChildEntity.__dict__] (38-й рядок коду) дозволяє нам дізнатися імена властивостей, які слід внести до списків [included_keys] та [excluded_keys]. Зверніть увагу, що, як видно з другого рядка результатів, залежно від того, чи властивість визначена всередині класу за допомогою геттера/сеттера, чи створена так, як створюється ключ словника, перед нею може бути або не бути префікс із назвою класу [ChildEntity].