13. Klasy generyczne [BaseEntity] i [MyException]
Zdefiniujemy teraz dwie klasy, z których będziemy regularnie korzystać w dalszej części.

13.1. Klasa MyException
Klasa [MyException] (MyException.py) udostępnia własną klasę wyjątków:
# własna klasa wyjątków wywodząca się z [BaseException]
class MyException(BaseException):
# konstruktor
def __init__(self: object, code: int, message: str):
# klasa nadrzędna
BaseException.__init__(self, message)
# kod błędu
self.code = code
# toString
def __str__(self):
return f"MyException[{self.code}, {super().__str__()}]"
# metoda pobierająca
@property
def code(self) -> int:
return self.__code
# setter
@code.setter
def code(self, code: int):
# kod błędu musi być dodatnią liczbą całkowitą
if isinstance(code, int) and code > 0:
self.__code = code
else:
# wyjątek
raise BaseException(f"code erreur {code} incorrect")
Uwagi
- wiersz 2: klasa [MyException] wywodzi się z predefiniowanej klasy [BaseException];
- wiersz 4: konstruktor przyjmuje dwa parametry:
- [code]: kod błędu typu całkowitego;
- [message]: komunikat o błędzie;
- wiersz 6: przekazujemy komunikat o błędzie do klasy nadrzędnej;
- wiersze 14–27: atrybut [code] jest obsługiwany za pomocą metod getter/setter;
- wiersze 23–24: sprawdzana jest poprawność atrybutu [code]: musi to być liczba całkowita większa od 0;
13.2. Klasa [BaseEntity]
Klasa [BaseEntity] będzie klasą nadrzędną dla większości klas, które utworzymy w celu enkapsulacji informacji o obiekcie. W dalszej części będziemy korzystać głównie z dwóch typów klas:
- klasy, których jedynym celem jest hermetyzacja w jednym miejscu informacji o tym samym obiekcie. Nie będą one posiadały żadnych zachowań (metod) poza metodami pobierającymi i ustawiającymi oraz funkcją wyświetlania (__str__). Jeśli mamy do zarządzania N obiektów, klasy te są instancjonowane N razy. [BaseEntity] będzie klasą nadrzędną dla tego typu klas;
- klasy, których główną rolą jest hermetyzacja metod i bardzo niewielkiej ilości informacji. Klasy te będą instancjonowane tylko raz (singleton). Ich rolą jest implementacja algorytmów aplikacji;
Klasa [BaseEntity] wygląda następująco:
# importy
import json
import re
from MyException import MyException
class BaseEntity(object):
# właściwości wykluczone ze stanu klasy
excluded_keys = []
# właściwości klasy
@staticmethod
def get_allowed_keys() -> list:
# id: identyfikator obiektu
return ["id"]
# toString
def __str__(self) -> str:
return self.asjson()
# metoda pobierająca
@property
def id(self) -> int:
return self.__id
# setter
@id.setter
def id(self, id):
# identyfikator musi być liczbą całkowitą >=0
try:
id = int(id)
erreur = id < 0
except:
erreur = True
# błąd?
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):
…
Komentarze
- Celem klasy [BaseEntity] jest ułatwienie konwersji obiekt/słownik oraz obiekt/jSON. W tym celu udostępniono następujące metody:
- [asdict]: zwraca słownik właściwości obiektu;
- [fromdict]: tworzy obiekt na podstawie słownika;
- [asjson]: zwraca ciąg znaków jSON obiektu, tak jak robi to funkcja [__str__];
- [fromjson]: tworzy obiekt na podstawie jego ciągu znaków jSON;
- klasa [BaseEntity] jest przeznaczona do tworzenia klas pochodnych, a nie do użycia w obecnej postaci;
- wiersze 22–25: klasa [BaseEntity] ma tylko jedną właściwość – liczbę całkowitą [id]. Ta właściwość stanowi identyfikator obiektu. W praktyce często przydatna jest możliwość rozróżnienia instancji tej samej klasy. Zrobimy to za pomocą tej właściwości, która jest unikalna dla każdej instancji. Ponadto obiekty często pochodzą z baz danych, gdzie są identyfikowane za pomocą klucza głównego, zazwyczaj liczby całkowitej. W takich przypadkach [id] będzie kluczem głównym;
- wiersze 27–40: metoda ustawiająca właściwość [id]. Sprawdzamy, czy jest to liczba całkowita >= 0. Jeśli tak nie jest, zgłaszany jest wyjątek typu [MyException] (wiersz 39);
- wiersz 10: [excluded_keys] jest atrybutem klasy, a nie instancji. W związku z tym należy zapisać [BaseEntity.excluded_keys]. Ten atrybut klasy jest listą zawierającą właściwości klasy, które nie biorą udziału w konwersjach Obiekt / Słownik oraz Obiekt / jSON;
- wiersze 12–16: [get_allowed_keys] zwraca listę właściwości klasy. Podczas konwersji Słownik → Obiekt lub jSON → Obiekt akceptowane będą wyłącznie klucze znajdujące się na tej liście. Każda klasa wywodząca się z klasy [BaseEntity] będzie musiała na nowo zdefiniować tę listę;
Należy tutaj zrozumieć, że właściwości i funkcje klasy [BaseEntity] są dostępne dla klas pochodnych od [BaseEntity]. Jest to istotna kwestia, którą należy zrozumieć.
Omówimy szczegółowo kod klasy [BaseEntity]. Jest on dość zaawansowany. Początkujący czytelnik może zadowolić się zapoznaniem się z rolą każdej funkcji, nie zagłębiając się w jej kod.
13.2.1. Metoda [BaseEntity.fromdict]
13.2.1.1. Définition
Metoda [fromdict] pozwala zainicjować obiekt [BaseEntity] lub obiekt pochodny na podstawie słownika:
def fromdict(self, state: dict, silent=False):
# aktualizujemy obiekt
# dozwolone klucze
allowed_keys = self.__class__.get_allowed_keys()
# przegląda klucze stanu
for key, value in state.items():
# czy klucz jest dozwolony?
if key not in allowed_keys:
if not silent:
raise MyException(2, f"la clé {key} n'est pas autorisée")
else:
# próbuje się przypisać wartość do klucza
# pozwalamy na zgłoszenie ewentualnego wyjątku
setattr(self, key, value)
# zwracamy obiekt
return self
Komentarze
- wiersz 1: funkcja otrzymuje jako parametr słownik [state], na podstawie którego zostanie zainicjowany bieżący obiekt;
- wiersz 4: wywoływana jest funkcja statyczna [get_allowed_keys] klasy, która wywołała funkcję [fromdict]. Jeśli mamy do czynienia z klasą pochodną od [BaseEntity], a ta klasa pochodna przedefiniowała funkcję statyczną [get_allowed_keys], wówczas wywoływana jest funkcja [get_allowed_keys]. Każda klasa pochodna redefiniuje tę funkcję statyczną, aby zadeklarować w niej swoje właściwości;
- wiersz 6: przeglądane są klucze i wartości słownika [state];
- wiersz 8: jeśli klucz [key] nie należy do właściwości klasy, to albo:
- jest ona ignorowana;
- rzuca się wyjątek (wiersz 10). Programista określa swoje intencje, przekazując odpowiedni parametr [silent] (wiersz 1). Wartość domyślna [silent] powoduje, że w przypadku próby zainicjowania obiektu za pomocą właściwości, której nie posiada, generowany jest wyjątek;
- wiersz 14: jeśli klucz należy do właściwości obiektu, wówczas przypisuje się go do obiektu [self] za pomocą predefiniowanej funkcji [setattr];
- wiersz 16: funkcja zwraca zainicjowany obiekt;
13.2.1.2. Exemples

13.2.1.2.1. Klasa [Utils]
Klasa [Utils] (Utils.py) ma następującą postać:
class Utils:
# metoda statyczna
@staticmethod
def is_string_ok(string: str) -> bool:
# czy string jest ciągiem znaków
erreur = not isinstance(string, str)
if not erreur:
# czy ciąg znaków jest pusty?
erreur = string.strip() == ''
# wynik
return not erreur
W wierszach 3–11 definiuje ona metodę statyczną, która zwraca wartość logiczną „prawda”, jeśli jej parametr [str] jest niepustym ciągiem znaków;
13.2.1.2.2. Klasa [Personne]
Klasa [Personne] (Personne.py) wywodzi się z klasy [BaseEntity]:
# importy
from BaseEntity import BaseEntity
from MyException import MyException
from Utils import Utils
# klasa Osoba
class Personne(BaseEntity):
# właściwości wykluczone ze stanu klasy
excluded_keys = []
# właściwości klasy
# id: identyfikator osoby
# imię: imię osoby
# nazwisko: nazwisko osoby
# wiek: wiek osoby
@staticmethod
def get_allowed_keys() -> list:
# id: identyfikator obiektu
return BaseEntity.get_allowed_keys() + ["nom", "prénom", "âge"]
# metody pobierające
@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
# metody ustawiające
@prénom.setter
def prénom(self, prénom: str):
# imię musi być niepuste
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):
# imię nie może być puste
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):
# wiek musi być liczbą całkowitą >=0
erreur = False
if isinstance(âge, int):
if âge >= 0:
self.__âge = âge
else:
erreur = True
else:
erreur = True
# błąd?
if erreur:
raise MyException(13, "L'âge doit être un entier >=0")
- wiersz 8: klasa [Personne] wywodzi się z klasy [BaseEntity];
- wiersze 8–65: zachowano zasadniczą część klasy [Personne], którą już znamy. Różnice są następujące:
- klasa nie posiada już konstruktora;
- klasa wykorzystuje wyjątek [MyException], przykład w wierszu 65;
- posiada metodę statyczną [get_allowed_keys] (wiersze 17–20), która definiuje listę jej właściwości. Właściwości charakterystyczne dla klasy [Personne] są dodawane do właściwości klasy nadrzędnej [BaseEntity];
- posiada statyczną listę [excluded_keys], do której jeszcze wrócimy;
13.2.1.2.3. Klasa [Enseignant]
Klasa [Enseignant] (Enseignant.py) wywodzi się z klasy [Personne]:
# importy
from MyException import MyException
from Personne import Personne
from Utils import Utils
# klasa Nauczyciel
class Enseignant(Personne):
# właściwości wykluczone ze stanu klasy
excluded_keys = []
# właściwości klasy
# id: identyfikator osoby
# imię: imię osoby
# nazwisko: nazwisko osoby
# wiek: wiek osoby
# przedmiot: nauczany przedmiot
@staticmethod
def get_allowed_keys() -> list:
# id: identyfikator obiektu
return Personne.get_allowed_keys() + ["discipline"]
# właściwości
@property
def discipline(self) -> str:
return self.__discipline
@discipline.setter
def discipline(self, discipline: str):
# przedmiot musi być niepustym ciągiem znaków
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")
# metoda show
def show(self):
print(f"Enseignant[{self.id}, {self.prénom}, {self.nom}, {self.âge}]")
- wiersz 8: klasa [Enseignant] rozszerza (lub wywodzi się z) klasy [Personne];
- wiersze 18–21: definiują listę właściwości klasy;
- wiersze 37–38: metoda [show] wyświetla tożsamość nauczyciela;
13.2.1.2.4. Konfiguracja [config]
Przykładowe skrypty wykorzystują następującą konfigurację [config]:
def configure():
import os
# katalog pliku konfiguracyjnego
script_dir = os.path.dirname(os.path.abspath(__file__))
# absolutne ścieżki do katalogów, które mają zostać dodane do syspath
absolute_dependencies = [
# klasa BaseEntity
f"{script_dir}/entities",
]
# aktualizacja ścieżki syspath
from myutils import set_syspath
set_syspath(absolute_dependencies)
# konfiguracja
return {}
- wiersze 8–10: foldery zawierające zależności projektu;
- wiersze 14–15: tworzona jest ścieżka Python Path;
- wiersz 18: zwracany jest pusty słownik (nie ma innych konfiguracji poza syspath);
13.2.1.2.5. Skrypt [fromdict_01]
Skrypt [fromdict_01] wygląda następująco:
# konfigurujemy aplikację
import config
config = config.configure()
# ścieżka systemowa jest skonfigurowana – można przeprowadzić importy
from Enseignant import Enseignant
# nauczyciel
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56})
enseignant1.show()
- wiersz 10: tworzy się obiekt [Enseignant] na podstawie słownika. W tym celu wykorzystuje się domyślny konstruktor klasy do utworzenia obiektu [Enseignant], do którego stosuje się metodę [fromdict]. Należy pamiętać, że w tym przypadku wykonywana jest metoda [fromdict] należąca do klasy nadrzędnej [BaseEntity]. Otóż:
- najpierw szukana jest metoda [fromdict] w klasie [Enseignant]. Nie istnieje;
- następnie jest ona wyszukiwana w klasie nadrzędnej [Personne]. Nie istnieje;
- następnie jest ona wyszukiwana w klasie nadrzędnej [BaseEntity]. Istnieje;
- wiersz 11: wyświetlany jest obiekt [Enseignant];
Wyniki są następujące:
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. Skrypt [fromdict_02]
Skrypt [fromdict_02] ma następującą treść:
# konfiguruje się aplikację
import config
config = config.configure()
# ścieżka systemowa została skonfigurowana – można przeprowadzić importy
from Enseignant import Enseignant
# nauczyciel
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "", "âge": 56})
enseignant1.show()
- wiersz 10: tworzymy nauczyciela z pustym imieniem. Powinno to spowodować wyjątek, ponieważ klasa [Personne] nie akceptuje pustych imion. Ten przykład pokazuje różnicę między słownikiem a obiektem. Obiekt może sprawdzić poprawność swoich właściwości, a słownik nie;
Wyniki są następujące:
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. Skrypt [fromdict_03]
Skrypt [fromdict_03] wygląda następująco:
# konfiguruje się aplikację
import config
config = config.configure()
# ścieżka systemowa jest skonfigurowana – można przeprowadzić importy
from Enseignant import Enseignant
# nauczyciel
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "albert", "âge": 56, "sexe": "M"})
enseignant1.show()
- wiersz 10: tworzymy nauczyciela na podstawie słownika zawierającego klucz (płeć), który nie należy do klasy [Enseignant]. Powinien wtedy zostać zgłoszony wyjątek;
Wyniki są następujące:
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. Skrypt [fromdict_04]
Skrypt [fromdict_04] jest kopią skryptu [fromdict_03] z jednym wyjątkiem:
# konfiguruje się aplikację
import config
config = config.configure()
# ścieżka systemowa jest skonfigurowana – można przeprowadzić importy
from Enseignant import Enseignant
# nauczyciel
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "albert", "âge": 56, "sexe": "M"}, silent=True)
enseignant1.show()
- wiersz 10: użyto parametru [silent=True], aby wskazać, że jeśli klucz ze słownika nie jest właściwością klasy [Enseignant], należy go po prostu zignorować. W takim przypadku nie zostanie zgłoszony żaden wyjątek;
Wyniki są następujące:
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. Metoda [BaseEntity.asdict]
13.2.2.1. Définition
Metoda [BaseEntity.asdict] zwraca słownik, którego klucze są właściwościami obiektu:
def asdict(self, included_keys: list = None, excluded_keys: list =[]) -> dict:
# atrybuty obiektu
attributes = self.__dict__
# nowe atrybuty
new_attributes = {}
# przeglądamy atrybuty
for key, value in attributes.items():
# jeśli klucz jest wyraźnie wymagany
if included_keys and key in included_keys:
self.set_value(key, value, new_attributes)
# w przeciwnym razie, jeśli klucz nie jest wykluczony
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)
# zwracamy słownik atrybutów
return new_attributes
Komentarze
- wiersz 1: funkcja [asdict] zwraca słownik właściwości obiektu;
- wiersz 1: [included_keys]: lista kluczy, które mają zostać uwzględnione w słowniku;
- wiersz 1: [excluded_keys]: lista kluczy, które należy wykluczyć ze słownika;
- wiersz 3: właściwość [self.__dict__] zwraca słownik właściwości obiektu. Nazwy właściwości są kluczami, a ich wartości – wartościami słownika. Obiekt może zawierać odwołania do innych obiektów. Nazwy właściwości są wówczas poprzedzone prefiksem nazwy klasy, do której należą. Nie jest to pożądane. Chcemy, aby właściwości występowały bez tego prefiksu;
- wiersz 3: należy tutaj zrozumieć, że jeśli funkcja [asdict] jest wykonywana wewnątrz klasy pochodnej od [BaseEntity], to właściwość [self.__dict__] zwraca słownik właściwości obiektu pochodnego;
- wiersz 5: słownik, który zamierzamy utworzyć;
- wiersz 7: przeglądamy wartości [self.__dict__] w postaci (klucz, wartość);
- wiersz 9: jeśli bieżący klucz znajduje się na liście kluczy do uwzględnienia, wówczas jest on dodawany do słownika [new_attributes] za pomocą funkcji [set_value], którą wkrótce opiszemy;
- wiersz 12: jeśli parametr [included_keys] nie występuje, wówczas wykorzystywany jest parametr [excluded_keys]. Jeśli właściwość nie znajduje się na liście właściwości do wykluczenia, wówczas jest dodawana do słownika [new_attributes];
- wiersz 12: istnieje kilka sposobów wykluczenia właściwości ze słownika:
- została zdefiniowana na poziomie atrybutu klasy [excluded_keys];
- została zdefiniowana na liście [excluded_keys] przekazanej do funkcji [asdict];
- parametr [included_keys] jest obecny i nie zawiera tej właściwości;
- wiersz 15: zwracamy słownik [new_attributes]
Funkcja [set_value] z wierszy 10 i 13 ma następującą postać:
@staticmethod
def set_value(key: str, value, new_attributes: dict):
# klucze mogą mieć postać __Class__key
match = re.match("^.*?__(.*?)$", key)
if match:
# zapisujemy nowy klucz
newkey = match.groups()[0]
else:
# klucz pozostaje bez zmian
newkey = key
# wstawiamy nowy klucz do słownika [new_attributes]
# w razie potrzeby przekształcając powiązaną wartość na jeden z typów
# słownik, lista, typ prosty
new_attributes[newkey] = BaseEntity.check_value(value)
Komentarze
- wiersz 4: sprawdzamy, czy klucz ma postać __Class_key. Taką postać ma, jeśli należy do obiektu zawartego w obiekcie głównym. W takim przypadku chcemy zachować tylko ciąg [key];
- wiersz 7: zachowujemy tylko ciąg znaków następujący po dwóch ostatnich podkreślonych znakach ciągu;
- wiersze 8–10: jeśli klucz nie ma postaci __Class_key, to zachowuje się go bez zmian;
- wiersze 11–14: wartość powiązana z kluczem [newkey] jest obliczana za pomocą metody statycznej [BaseEntity.check_value];
Metoda statyczna [BaseEntity.check_value] wygląda następująco:
@staticmethod
def check_value(value):
# wartość może być typu BaseEntity, list, dict lub typu prostego
# czy wartość jest instancją typu BaseEntity?
if isinstance(value, BaseEntity):
value2 = value.asdict()
# czy wartość jest typu list?
elif isinstance(value, list):
value2 = BaseEntity.list2list(value)
# czy wartość jest typu dict?
elif isinstance(value, dict):
value2 = BaseEntity.dict2dict(value)
#jest typem prostym?
else:
value2 = value
# zwracamy wynik
return value2
- wiersz 1: metoda [check_value] jest statyczna (metoda klasy, a nie instancji). Otrzymuje ona jako parametr wartość, która ma zostać przypisana do klucza słownika:
- wiersz 17: jeśli ta wartość jest typem prostym, pozostaje niezmieniona;
- wiersze 5–6: jeśli ta wartość jest typu BaseEntity, wartość ta zostaje zastąpiona przez swój słownik. Mamy wtedy do czynienia z wywołaniem rekurencyjnym;
- wiersze 8–9: jeśli ta wartość jest listą, wówczas zostaje zastąpiona wartością [BaseEntity.list2list];
- wiersze 11–12: jeśli ta wartość jest słownikiem, wówczas zostaje zastąpiona wartością [BaseEntity.dict2dict];
Metoda statyczna [BaseEntity.list2list] ma następującą postać:
@staticmethod
def list2list(liste: list) -> list:
# sprawdzamy elementy listy
newlist = []
for value in liste:
newlist.append(BaseEntity.check_value(value))
# zwracamy nową listę
return newlist
- wiersz 2: metoda przyjmuje listę i zwraca listę;
- wiersze 5–6: każda wartość z listy przekazanej jako parametr jest zastępowana wartością zwracaną przez metodę statyczną [BaseEntity.check_value]. Mamy więc do czynienia z wywołaniem rekurencyjnym. Metoda statyczna [BaseEntity.check_value] jest wywoływana tak długo, aż jej parametr [value] będzie typem prostym (a nie typem BaseEntity, listą ani słownikiem);
Metoda statyczna [BaseEntity.dict2dict] ma następującą postać:
@staticmethod
def dict2dict(dictionary: dict) -> dict:
# sprawdzamy elementy słownika
newdict = {}
for key, value in dictionary.items():
newdict[key] = BaseEntity.check_value(value)
# zwracamy nowy słownik
return newdict
- wiersz 2: metoda przyjmuje słownik i zwraca słownik;
- wiersze 5–6: każda wartość słownika przekazanego jako parametr jest zastępowana wartością zwracaną przez metodę statyczną [BaseEntity.check_value]. Mamy więc do czynienia z wywołaniem rekurencyjnym. Metoda statyczna [BaseEntity.check_value] jest wywoływana tak długo, aż jej parametr [value] będzie typem prostym (a nie typem BaseEntity, listą ani słownikiem);
13.2.2.2. Exemples
Skrypt [asdict_01] pokazuje różne zastosowania metody [asdict]:
# konfiguruje się aplikację
import config
config = config.configure()
# ścieżka systemowa jest skonfigurowana – można przeprowadzić importy
from Enseignant import Enseignant
from BaseEntity import BaseEntity
# nauczyciel
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)
# kolejny nauczyciel
enseignant2 = Enseignant().fromdict({"id": 2, "nom": "abélard", "prénom": "béatrice", "âge": 57})
print(enseignant2.asdict())
print(enseignant2.asdict(included_keys=["_Personne__nom"]))
# lista jednostek w jednostce
Enseignant.excluded_keys = []
entity1 = BaseEntity()
enseignants = [enseignant1, enseignant2]
setattr(entity1, "enseignants", enseignants)
print(entity1.asdict())
# słownik encji w encji
matières = {"maths": enseignant1, "français": enseignant2}
setattr(entity1, "matières", matières)
print(entity1.asdict())
Wyniki wykonania są następujące:
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
- wiersz 4 pokazuje zalety metody [asdict] w porównaniu z wykorzystaniem właściwości [__dict__]. Właściwości są pozbawione prefiksu swojej klasy. Ułatwia to ich wyświetlanie;
- istnieje kilka sposobów wykorzystania metody [asdict]:
- jeśli chcemy uzyskać wszystkie właściwości, używamy metody [asdict] bez parametrów;
- chcemy tylko niektóre właściwości:
- jeśli jest więcej właściwości do uwzględnienia niż do wykluczenia: należy użyć jedynego parametru [excluded_keys];
- jeśli jest mniej właściwości do uwzględnienia niż do wykluczenia: należy użyć metody z jednym parametrem [included_keys];
13.2.3. Metoda [BaseEntity.asjson]
Metoda ta pozwala uzyskać ciąg jSON z obiektu [BaseEntity] lub obiektu pochodnego. Wyświetla ona ciąg jSON ze słownika zwróconego przez metodę [asdict]. Jej kod jest następujący:
def asjson(self, included_keys: list = None, excluded_keys: list = []) -> str:
# ciąg JSON
return json.dumps(self.asdict(included_keys=included_keys, excluded_keys=excluded_keys), ensure_ascii=False)
- wiersz 1: parametry metody [asjson] są takie same jak parametry metody [asdict];
Oto przykład (asjson_01) wykorzystujący tę metodę:
# konfigurujemy aplikację
import config
config = config.configure()
# ścieżka syspath jest skonfigurowana – można przeprowadzić importy
from Enseignant import Enseignant
from BaseEntity import BaseEntity
# nauczyciel
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())
# kolejny nauczyciel
enseignant2 = Enseignant().fromdict({"id": 2, "nom": "abélard", "prénom": "béatrice", "âge": 57})
print(enseignant2.asjson())
print(enseignant2.asjson(included_keys=["_Personne__nom"]))
# lista elementów w elemencie
Enseignant.excluded_keys = []
entity1 = BaseEntity()
enseignants = [enseignant1, enseignant2]
setattr(entity1, "enseignants", enseignants)
print(entity1.asjson())
# słownik jednostek w jednostce
matières = {"maths": enseignant1, "français": enseignant2}
setattr(entity1, "matières", matières)
print(entity1.asjson())
Wyniki są następujące:
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
Metoda [BaseEntity.__str__] wykorzystuje metodę [asjson] do wyświetlenia identyfikatora obiektu [BaseEntity] lub obiektu pochodnego:
# toString
def __str__(self) -> str:
return self.asjson()
13.2.4. Metoda [BaseEntity.fromjson]
Metoda [BaseEntity.fromjson] umożliwia zainicjowanie obiektu typu [BaseEntity] lub pochodnego na podstawie słownika jSON. Jej kod jest następujący:
def fromjson(self, json_state: str, silent: bool = False):
# aktualizuje się stan obiektu na podstawie ciągu znaków jSON
return self.fromdict(json.loads(json_state), silent=silent)
- wiersz 1: metoda przyjmuje dwa parametry:
- [json_state]: słownik jSON, który posłuży do zainicjowania obiektu [BaseEntity];
- [silent]: wskazujący, czy obecność w słowniku jSON klucza, który nie może zostać zaakceptowany jako właściwość obiektu [BaseEntity], powoduje wyjątek (silent=False) czy jest po prostu ignorowana (silent=True);
- wiersz 3: najpierw tworzymy słownik Python będący odwzorowaniem słownika jSON, a następnie używamy metody [fromdict] do zainicjowania obiektu [BaseEntity] na podstawie tego słownika Python;
Oto przykład (fromjson_01):
# konfiguruje się aplikację
import config
config = config.configure()
# ścieżka systemowa jest skonfigurowana – można przeprowadzić importy
from Enseignant import Enseignant
import json
# nauczyciel
json1 = json.dumps({"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56})
enseignant1 = Enseignant().fromjson(json1)
enseignant1.show()
- wiersz 11: tworzymy ciąg znaków jSON na podstawie słownika;
- wiersz 12: obiekt [Enseignant] jest inicjowany tym ciągiem znaków;
- wiersz 13: wyświetlany jest nauczyciel;
Wyniki są następujące:
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. Skrypt [main]
Skrypt [main] podsumowuje różne metody, z którymi się spotkaliśmy:
# konfiguruje się aplikację
import config
config = config.configure()
# ścieżka systemowa jest skonfigurowana – można przeprowadzić importy
from BaseEntity import BaseEntity
from MyException import MyException
# klasa
class ChildEntity(BaseEntity):
# atrybuty wykluczone ze stanu klasy
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})")
# konfiguracja ChildEntity
ChildEntity.excluded_keys = []
# instancja ChildEntity
child = ChildEntity().fromdict({"att1": 1, "att2": 2})
# należy zwrócić uwagę na nazwy właściwości
# to właśnie te nazwy są używane w [excluded_keys] i [included_keys]
print(child.__dict__)
# właściwości bez przedrostka klasy
print(child)
# instancja ChildEntity
try:
child = ChildEntity().fromdict({"att1": 1, "att5": 5})
print(child)
except MyException as erreur:
print(erreur)
# instancja ChildEntity
child = ChildEntity().fromdict({"att1": 1, "att2": 2, "att3": 3, "att4": 4})
print(child)
# wykluczenie niektórych kluczy ze stanu instancji
ChildEntity.excluded_keys = ['att3']
print(child)
# klucz jest wyraźnie wykluczony z wyświetlania
# klucz ten dołącza się do tych wykluczonych globalnie na poziomie klasy
print(child.asdict(excluded_keys=["_ChildEntity__att1"]))
print(child.asjson(excluded_keys=["att2"]))
# związek klasy ze słownikiem
# może ona sprawdzić poprawność swojej zawartości
try:
child = ChildEntity().fromdict({"att1": 20})
except MyException as erreur:
print(erreur)
# instancja ChildEntity
child1 = ChildEntity().fromdict({"att1": 1, "att2": 2, "att3": 3, "att4": 4})
# instancja ChildEntity zawierająca inną instancję ChildEntity
child2 = ChildEntity().fromdict({"att1": 10, "att2": 20, "att3": 30, "att4": child1})
print(child2)
# included_keys ma pierwszeństwo przed excluded_keys, które są wówczas ignorowane
ChildEntity.excluded_keys = ['_ChildEntity__att1', 'att2']
print(child.asdict(included_keys=["_ChildEntity__att1", "att3"], excluded_keys=["att3", "att4"]))
Wyniki wykonania są następujące:
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
Zwróćmy uwagę na wiersz 2 wyników: to właśnie właściwość [ChildEntity.__dict__] (wiersz 38 kodu) pozwala nam poznać nazwy właściwości, które należy umieścić na listach [included_keys] i [excluded_keys]. Warto zauważyć, również w wierszu 2 wyników, że w zależności od tego, czy właściwość jest zdefiniowana wewnątrz klasy za pomocą metod getter/setter, czy też została utworzona tak, jak tworzy się klucz w słowniku, jest ona poprzedzona nazwą klasy [ChildEntity] lub nie.