13. Генеральні класи [BaseEntity] та [MyException]
Тепер ми визначимо два класи, які надалі будемо регулярно використовувати.

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

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].