13. De generieke klassen [BaseEntity] en [MyException]
We definiëren nu twee klassen die we hierna regelmatig zullen gebruiken.

13.1. De klasse MyException
De klasse [MyException] (MyException.py) biedt een eigen uitzonderingsklasse:
# een eigen uitzonderingsklasse die afleidt van [BaseException]
class MyException(BaseException):
# constructor
def __init__(self: object, code: int, message: str):
# bovenliggende klasse
BaseException.__init__(self, message)
# foutcode
self.code = code
# toString
def __str__(self):
return f"MyException[{self.code}, {super().__str__()}]"
# getter
@property
def code(self) -> int:
return self.__code
# setter
@code.setter
def code(self, code: int):
# de foutcode moet een positief geheel getal zijn
if isinstance(code, int) and code > 0:
self.__code = code
else:
# uitzondering
raise BaseException(f"code erreur {code} incorrect")
Opmerkingen
- regel 2: de klasse [MyException] is afgeleid van de vooraf gedefinieerde klasse [BaseException];
- regel 4: de constructor accepteert twee parameters:
- [code]: een gehele foutcode;
- [message]: een foutmelding;
- regel 6: de foutmelding wordt doorgegeven aan de bovenliggende klasse;
- regels 14-27: het attribuut [code] wordt bewerkt via een getter/setter;
- regels 23-24: de geldigheid van het attribuut [code] wordt gecontroleerd: het moet een geheel getal >0 zijn;
13.2. De klasse [BaseEntity]
De klasse [BaseEntity] zal de bovenliggende klasse zijn van de meeste klassen die we zullen maken om informatie over een object in te kapselen. In het vervolg zullen we voornamelijk twee soorten klassen gebruiken:
- klassen die uitsluitend bedoeld zijn om informatie over één en hetzelfde object op één plek te verpakken. Deze zullen geen andere gedragingen (methoden) hebben dan getters/setters en een weergavefunctie (__str__). Als er N objecten moeten worden beheerd, worden deze klassen N keer geïnstantieerd. [BaseEntity] zal de bovenliggende klasse zijn van dit type klassen;
- klassen waarvan de belangrijkste rol bestaat uit het inkapselen van methoden en zeer weinig informatie. Deze klassen worden slechts één keer geïnstantieerd (singleton). Hun rol is het implementeren van de algoritmen van een applicatie;
De klasse [BaseEntity] ziet er als volgt uit:
# imports
import json
import re
from MyException import MyException
class BaseEntity(object):
# eigenschappen die zijn uitgesloten van de klassestatus
excluded_keys = []
# eigenschappen van de klasse
@staticmethod
def get_allowed_keys() -> list:
# id: identificatiecode van het object
return ["id"]
# toString
def __str__(self) -> str:
return self.asjson()
# getter
@property
def id(self) -> int:
return self.__id
# setter
@id.setter
def id(self, id):
# de id moet een geheel getal zijn >=0
try:
id = int(id)
erreur = id < 0
except:
erreur = True
# fout?
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):
…
Opmerkingen
- het doel van de klasse [BaseEntity] is het vergemakkelijken van de conversies Object / Dictieon en Object / jSON. Er worden dus de volgende methoden aangeboden:
- [asdict]: retourneert het woordenboek met de eigenschappen van het object;
- [fromdict]: maakt een object aan op basis van een woordenboek;
- [asjson]: retourneert de tekenreeks jSON van het object, net zoals de functie [__str__] dat doet;
- [fromjson]: maakt een object aan op basis van de tekenreeks jSON;
- de klasse [BaseEntity] is bedoeld om van af te leiden en niet om als zodanig te worden gebruikt;
- regels 22-25: de klasse [BaseEntity] heeft slechts één eigenschap, het gehele getal [id]. Deze eigenschap is de identificatiecode van het object. In de praktijk is het vaak handig om instanties van dezelfde klasse van elkaar te kunnen onderscheiden. Dat doen we met deze eigenschap, die uniek is voor een instantie. Bovendien zijn objecten vaak afkomstig uit databases waar ze worden geïdentificeerd door een primaire sleutel, meestal een geheel getal. In die gevallen is [id] de primaire sleutel;
- regels 27-40: de setter van de eigenschap [id]. Er wordt gecontroleerd of het een geheel getal >=0 is. Als dat niet het geval is, wordt er een uitzondering van het type [MyException] gegenereerd (regel 39);
- regel 10: [excluded_keys] is een klasse-attribuut en geen instantie-attribuut. Daarom schrijven we [BaseEntity.excluded_keys]. Dit klasse-attribuut is een lijst met de klasse-eigenschappen die niet betrokken zijn bij de conversies Object / Woordenboek en Object / jSON;
- regels 12-16: [get_allowed_keys] geeft de lijst met eigenschappen van de klasse weer. Bij een conversie van woordenboek naar object of van jSON naar object worden alleen de sleutels geaccepteerd die in deze lijst voorkomen. Elke klasse die is afgeleid van de klasse [BaseEntity] moet deze lijst opnieuw definiëren;
Het is belangrijk om te begrijpen dat de eigenschappen en functies van de klasse [BaseEntity] toegankelijk zijn voor klassen die zijn afgeleid van [BaseEntity]. Dit is het belangrijkste punt om te begrijpen.
We gaan de code van de klasse [BaseEntity] in detail bespreken. Deze is vrij geavanceerd. De beginnende lezer kan volstaan met het lezen van de functie van elke methode, zonder zich te verdiepen in de code ervan.
13.2.1. De methode [BaseEntity.fromdict]
13.2.1.1. Définition
Met de methode [fromdict] kun je een object van het type [BaseEntity] of een daarvan afgeleid object initialiseren op basis van een woordenboek:
def fromdict(self, state: dict, silent=False):
# het object wordt bijgewerkt
# toegestane sleutels
allowed_keys = self.__class__.get_allowed_keys()
# doorzoekt status-sleutels
for key, value in state.items():
# is de sleutel toegestaan?
if key not in allowed_keys:
if not silent:
raise MyException(2, f"la clé {key} n'est pas autorisée")
else:
# er wordt geprobeerd de waarde aan de sleutel toe te wijzen
# een eventuele uitzondering wordt doorgegeven
setattr(self, key, value)
# het object wordt teruggegeven
return self
Opmerkingen
- regel 1: de functie ontvangt als parameter het woordenboek [state] op basis waarvan het huidige object zal worden geïnitialiseerd;
- regel 4: er wordt een beroep gedaan op de statische functie [get_allowed_keys] van de klasse die de functie [fromdict] heeft aangeroepen. Als het gaat om een van [BaseEntity] afgeleide klasse en deze afgeleide klasse de statische functie [get_allowed_keys] heeft geherdefinieerd, dan wordt de functie [get_allowed_keys] aangeroepen. Elke afgeleide klasse herdefinieert deze statische functie om daarin haar eigenschappen te declareren;
- regel 6: de sleutels en waarden van het woordenboek [state] worden doorlopen;
- regel 8: als de sleutel [key] geen deel uitmaakt van de eigenschappen van de klasse, dan geldt:
- wordt deze genegeerd;
- er wordt een uitzondering gegenereerd (regel 10). De ontwikkelaar geeft aan wat hij wil door de juiste parameter [silent] door te geven (regel 1). De standaardwaarde van [silent] zorgt ervoor dat er een uitzondering wordt gegenereerd als wordt geprobeerd het object te initialiseren met een eigenschap die het niet heeft;
- regel 14: als de sleutel deel uitmaakt van de eigenschappen van het object, dan wordt deze aan het object [self] toegewezen met behulp van de vooraf gedefinieerde functie [setattr];
- regel 16: de functie retourneert het geïnitialiseerde object;
13.2.1.2. Exemples

13.2.1.2.1. De klasse [Utils]
De klasse [Utils] (Utils.py) is als volgt:
class Utils:
# statische methode
@staticmethod
def is_string_ok(string: str) -> bool:
# is string een tekenreeks
erreur = not isinstance(string, str)
if not erreur:
# is de tekenreeks leeg?
erreur = string.strip() == ''
# resultaat
return not erreur
Deze definieert in de regels 3-11 een statische methode die een booleaanse waarde ‘waar’ retourneert als de parameter [str] een niet-lege tekenreeks is;
13.2.1.2.2. De klasse [Personne]
De klasse [Personne] (Personne.py) is afgeleid van de klasse [BaseEntity]:
# imports
from BaseEntity import BaseEntity
from MyException import MyException
from Utils import Utils
# klasse Persoon
class Personne(BaseEntity):
# eigenschappen die zijn uitgesloten van de status van de klasse
excluded_keys = []
# eigenschappen van de klasse
# id: identificatiecode van de persoon
# voornaam: voornaam van de persoon
# achternaam: achternaam van de persoon
# leeftijd: leeftijd van de persoon
@staticmethod
def get_allowed_keys() -> list:
# id: identificatiecode van het object
return BaseEntity.get_allowed_keys() + ["nom", "prénom", "âge"]
# getters
@property
def prénom(self) -> str:
return self.__prénom
@property
def nom(self) -> str:
return self.__nom
@property
def âge(self) -> int:
return self.__âge
# setters
@prénom.setter
def prénom(self, prénom: str):
# de voornaam mag niet leeg zijn
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):
# de voornaam mag niet leeg zijn
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):
# de leeftijd moet een geheel getal zijn >=0
erreur = False
if isinstance(âge, int):
if âge >= 0:
self.__âge = âge
else:
erreur = True
else:
erreur = True
# fout?
if erreur:
raise MyException(13, "L'âge doit être un entier >=0")
- regel 8: de klasse [Personne] is afgeleid van de klasse [BaseEntity];
- regels 8-65: het grootste deel van de reeds besproken klasse [Personne] is behouden. De verschillen zijn als volgt:
- de klasse heeft geen constructor meer;
- de klasse maakt gebruik van de uitzondering [MyException], zie bijvoorbeeld regel 65;
- de klasse heeft een statische methode, [get_allowed_keys], regels 17-20, die de lijst met eigenschappen definieert. De eigenschappen die specifiek zijn voor de klasse [Personne] worden toegevoegd aan die van de bovenliggende klasse [BaseEntity];
- de klasse heeft een statische lijst [excluded_keys], waar we later op terugkomen;
13.2.1.2.3. De klasse [Enseignant]
De klasse [Enseignant] (Enseignant.py) is afgeleid van de klasse [Personne]:
# imports
from MyException import MyException
from Personne import Personne
from Utils import Utils
# klasse Leraar
class Enseignant(Personne):
# eigenschappen die niet in de status van de klasse zijn opgenomen
excluded_keys = []
# eigenschappen van de klasse
# id: identificatiecode van de persoon
# voornaam: voornaam van de persoon
# achternaam: achternaam van de persoon
# leeftijd: leeftijd van de persoon
# vak: onderwezen vak
@staticmethod
def get_allowed_keys() -> list:
# id: identificatiecode van het object
return Personne.get_allowed_keys() + ["discipline"]
# eigenschappen
@property
def discipline(self) -> str:
return self.__discipline
@discipline.setter
def discipline(self, discipline: str):
# het vak moet een niet-lege tekenreeks zijn
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-methode
def show(self):
print(f"Enseignant[{self.id}, {self.prénom}, {self.nom}, {self.âge}]")
- regel 8: de klasse [Enseignant] breidt de klasse [Personne] uit (of is daarvan afgeleid);
- regels 18-21: definiëren de lijst met eigenschappen van de klasse;
- regels 37-38: de methode [show] geeft de identiteit van de docent weer;
13.2.1.2.4. De configuratie [config]
De voorbeeldscripts maken gebruik van de volgende configuratie [config]:
def configure():
import os
# map van het configuratiebestand
script_dir = os.path.dirname(os.path.abspath(__file__))
# absolute paden van de mappen die in de syspath moeten worden opgenomen
absolute_dependencies = [
# de klasse BaseEntity
f"{script_dir}/entities",
]
# syspath bijwerken
from myutils import set_syspath
set_syspath(absolute_dependencies)
# de configuratie wordt doorgevoerd
return {}
- regels 8-10: de mappen met de afhankelijkheden van het project;
- regels 14-15: het Python-pad wordt samengesteld;
- regel 18: er wordt een leeg woordenboek geretourneerd (er is geen andere configuratie dan die van de syspath);
13.2.1.2.5. Het script [fromdict_01]
Het script [fromdict_01] ziet er als volgt uit:
# de applicatie wordt geconfigureerd
import config
config = config.configure()
# het syspath is geconfigureerd – we kunnen de imports uitvoeren
from Enseignant import Enseignant
# een docent
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56})
enseignant1.show()
- regel 10: er wordt een object [Enseignant] aangemaakt op basis van een woordenboek. Hiervoor wordt de standaardconstructor van de klasse gebruikt om een object [Enseignant] aan te maken, waarop vervolgens de methode [fromdict] wordt toegepast. Het is belangrijk te begrijpen dat de methode [fromdict] die hier wordt uitgevoerd, die van de bovenliggende klasse [BaseEntity] is. Immers:
- wordt eerst gezocht naar de methode [fromdict] in de klasse [Enseignant]. Deze bestaat niet;
- vervolgens wordt er gezocht in de bovenliggende klasse [Personne]. Deze bestaat niet;
- vervolgens wordt er gezocht in de bovenliggende klasse [BaseEntity]. Deze bestaat wel;
- regel 11: het object [Enseignant] wordt weergegeven;
De resultaten zijn als volgt:
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. Het script [fromdict_02]
Het script [fromdict_02] is als volgt:
# de applicatie wordt geconfigureerd
import config
config = config.configure()
# het syspath is geconfigureerd – de imports kunnen worden uitgevoerd
from Enseignant import Enseignant
# een docent
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "", "âge": 56})
enseignant1.show()
- regel 10: er wordt een docent aangemaakt met een leeg voornaamveld. Dit moet een uitzondering veroorzaken, omdat de klasse [Personne] geen lege voornamen accepteert. Dit voorbeeld laat het verschil zien tussen een woordenboek en een object. Het object kan de geldigheid van zijn eigenschappen controleren, het woordenboek niet;
De resultaten zijn als volgt:
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. Het script [fromdict_03]
Het script [fromdict_03] is als volgt:
# de applicatie wordt geconfigureerd
import config
config = config.configure()
# het syspath is geconfigureerd – de imports kunnen worden uitgevoerd
from Enseignant import Enseignant
# een docent
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "albert", "âge": 56, "sexe": "M"})
enseignant1.show()
- regel 10: er wordt een docent aangemaakt op basis van een woordenboek met een sleutel (geslacht) die niet tot de klasse [Enseignant] behoort. Er zou dan een uitzondering moeten worden gegenereerd;
De resultaten zijn als volgt:
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. Het script [fromdict_04]
Het script [fromdict_04] is een kopie van [fromdict_03], op één detail na:
# de applicatie wordt geconfigureerd
import config
config = config.configure()
# het syspath is geconfigureerd – de imports kunnen worden uitgevoerd
from Enseignant import Enseignant
# een docent
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "albert", "âge": 56, "sexe": "M"}, silent=True)
enseignant1.show()
- regel 10: de parameter [silent=True] is gebruikt om aan te geven dat als een sleutel uit het woordenboek geen eigenschap is van de klasse [Enseignant], deze gewoon genegeerd moet worden. In dat geval wordt er geen uitzondering gegenereerd;
De resultaten zijn als volgt:
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. De methode [BaseEntity.asdict]
13.2.2.1. Définition
De methode [BaseEntity.asdict] retourneert een woordenboek waarvan de sleutels de eigenschappen van het object zijn:
def asdict(self, included_keys: list = None, excluded_keys: list =[]) -> dict:
# attributen van het object
attributes = self.__dict__
# de nieuwe attributen
new_attributes = {}
# de attributen worden doorlopen
for key, value in attributes.items():
# als de sleutel expliciet wordt gevraagd
if included_keys and key in included_keys:
self.set_value(key, value, new_attributes)
# anders, als de sleutel niet is uitgesloten
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)
# het attributenwoordenboek wordt weergegeven
return new_attributes
Opmerkingen
- regel 1: de functie [asdict] retourneert het woordenboek met de eigenschappen van het object;
- regel 1: [included_keys]: de lijst met sleutels die in het woordenboek moeten worden opgenomen;
- regel 1: [excluded_keys]: de lijst met sleutels die uit het woordenboek moeten worden weggelaten;
- regel 3: de eigenschap [self.__dict__] retourneert het woordenboek met de eigenschappen van het object. De namen van de eigenschappen zijn de sleutels en hun waarden zijn de waarden van het woordenboek. Een object kan verwijzingen naar andere objecten bevatten. De namen van de eigenschappen worden dan voorafgegaan door de naam van de klasse waartoe ze behoren. Dat is iets wat we niet willen. We willen de eigenschappen zonder hun voorvoegsel;
- regel 3: hier moet je begrijpen dat als de functie [asdict] wordt uitgevoerd binnen een klasse die is afgeleid van [BaseEntity], de eigenschap [self.__dict__] het woordenboek met de eigenschappen van het afgeleide object retourneert;
- regel 5: het woordenboek dat we gaan samenstellen;
- regel 7: we doorlopen de waarden van [self.__dict__] in de vorm (sleutel, waarde);
- regel 9: als de huidige sleutel voorkomt in de lijst met op te nemen sleutels, wordt deze toegevoegd aan het woordenboek [new_attributes] door de functie [set_value], die we straks zullen beschrijven;
- regel 12: als de parameter [included_keys] niet aanwezig is, dan wordt de parameter [excluded_keys] gebruikt. Als de eigenschap niet tot de uit te sluiten eigenschappen behoort, dan wordt deze toegevoegd aan het woordenboek [new_attributes];
- regel 12: er zijn verschillende manieren om een eigenschap uit het woordenboek uit te sluiten:
- de eigenschap is gedefinieerd op het niveau van het klasse-attribuut [excluded_keys];
- de eigenschap is gedefinieerd in de lijst [excluded_keys] die aan de functie [asdict] is doorgegeven;
- de parameter [included_keys] is aanwezig en bevat de eigenschap niet;
- regel 15: het woordenboek [new_attributes] wordt teruggegeven
De functie [set_value] uit de regels 10 en 13 is als volgt:
@staticmethod
def set_value(key: str, value, new_attributes: dict):
# de sleutels kunnen de vorm __Class__key hebben
match = re.match("^.*?__(.*?)$", key)
if match:
# noteren we de nieuwe sleutel
newkey = match.groups()[0]
else:
# de sleutel blijft ongewijzigd
newkey = key
# de nieuwe sleutel wordt toegevoegd aan het woordenboek [new_attributes]
# waarbij de bijbehorende waarde indien nodig wordt omgezet naar een van de typen
# dict, list, eenvoudig type
new_attributes[newkey] = BaseEntity.check_value(value)
Opmerkingen
- regel 4: er wordt gekeken of de sleutel de vorm __Class_key heeft. Dit is de vorm die de sleutel heeft als deze behoort tot een object dat is opgenomen in het hoofdobject. In dat geval willen we alleen de tekenreeks [key] behouden;
- regel 7: we behouden alleen de tekenreeks die volgt op de laatste twee onderstreepte tekens van de tekenreeks;
- regel 8-10: als de sleutel niet de vorm __Class_key heeft, dan wordt deze ongewijzigd behouden;
- regels 11-14: de waarde die bij de sleutel [newkey] hoort, wordt berekend met de statische methode [BaseEntity.check_value];
De statische methode [BaseEntity.check_value] is als volgt:
@staticmethod
def check_value(value):
# de waarde kan van het type BaseEntity, list, dict of een eenvoudig type zijn
# Is de waarde een instantie van BaseEntity?
if isinstance(value, BaseEntity):
value2 = value.asdict()
# Is de waarde van het type list?
elif isinstance(value, list):
value2 = BaseEntity.list2list(value)
# Is de waarde van het type dict?
elif isinstance(value, dict):
value2 = BaseEntity.dict2dict(value)
# waarde is een eenvoudig type
else:
value2 = value
# het resultaat wordt weergegeven
return value2
- regel 1: de methode [check_value] is statisch (klasse- en geen instantiemethode). Deze methode ontvangt als parameter de waarde die aan een sleutel van het woordenboek moet worden gekoppeld:
- regel 17: als deze waarde een eenvoudig type is, blijft deze ongewijzigd;
- regels 5-6: als deze waarde van het type BaseEntity is, wordt de waarde vervangen door het bijbehorende woordenboek. Er vindt dan een recursieve aanroep plaats;
- regels 8-9: als deze waarde een lijst is, wordt deze vervangen door de waarde [BaseEntity.list2list];
- regels 11-12: als deze waarde een woordenboek is, wordt deze vervangen door de waarde [BaseEntity.dict2dict];
De statische methode [BaseEntity.list2list] is als volgt:
@staticmethod
def list2list(liste: list) -> list:
# we bekijken de elementen van de lijst
newlist = []
for value in liste:
newlist.append(BaseEntity.check_value(value))
# we geven de nieuwe lijst weer
return newlist
- regel 2: de methode ontvangt een lijst en retourneert een lijst;
- regels 5-6: elke waarde in de als parameter ontvangen lijst wordt vervangen door de waarde die wordt geretourneerd door de statische methode [BaseEntity.check_value]. We hebben hier dus te maken met een recursieve aanroep. De statische methode [BaseEntity.check_value] wordt aangeroepen totdat de parameter [value] een eenvoudig type is (geen type BaseEntity, list of dict);
De statische methode [BaseEntity.dict2dict] is als volgt:
@staticmethod
def dict2dict(dictionary: dict) -> dict:
# we inspecteren de elementen van het woordenboek
newdict = {}
for key, value in dictionary.items():
newdict[key] = BaseEntity.check_value(value)
# we geven het nieuwe woordenboek weer
return newdict
- regel 2: de methode ontvangt een woordenboek en retourneert een woordenboek;
- regels 5-6: elke waarde van het als parameter ontvangen woordenboek wordt vervangen door de waarde die wordt geretourneerd door de statische methode [BaseEntity.check_value]. We hebben hier dus te maken met een recursieve aanroep. De statische methode [BaseEntity.check_value] wordt aangeroepen totdat de parameter [value] een eenvoudig type is (geen type BaseEntity, list of dict);
13.2.2.2. Exemples
Het script [asdict_01] toont verschillende toepassingen van de methode [asdict]:
# de applicatie wordt geconfigureerd
import config
config = config.configure()
# het syspath is geconfigureerd – we kunnen de imports uitvoeren
from Enseignant import Enseignant
from BaseEntity import BaseEntity
# een docent
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)
# een andere docent
enseignant2 = Enseignant().fromdict({"id": 2, "nom": "abélard", "prénom": "béatrice", "âge": 57})
print(enseignant2.asdict())
print(enseignant2.asdict(included_keys=["_Personne__nom"]))
# een lijst met entiteiten binnen een entiteit
Enseignant.excluded_keys = []
entity1 = BaseEntity()
enseignants = [enseignant1, enseignant2]
setattr(entity1, "enseignants", enseignants)
print(entity1.asdict())
# een entiteitenwoordenboek binnen een entiteit
matières = {"maths": enseignant1, "français": enseignant2}
setattr(entity1, "matières", matières)
print(entity1.asdict())
De resultaten van de uitvoering zijn als volgt:
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
- regel 4 toont het voordeel van de methode [asdict] ten opzichte van het gebruik van de eigenschap [__dict__]. De eigenschappen zijn ontdaan van het voorvoegsel van hun klasse. Dit leent zich beter voor weergave;
- er zijn verschillende manieren om de methode [asdict] te gebruiken:
- als we alle eigenschappen willen: gebruiken we de methode [asdict] zonder parameters;
- als we alleen bepaalde eigenschappen willen:
- er zijn meer eigenschappen die moeten worden opgenomen dan uitgesloten: we gebruiken alleen de parameter [excluded_keys];
- er zijn minder eigenschappen die moeten worden opgenomen dan uitgesloten: we gebruiken alleen de parameter [included_keys];
13.2.3. De methode [BaseEntity.asjson]
Met deze methode kan de tekenreeks jSON worden verkregen uit een object [BaseEntity] of een daarvan afgeleid object. Deze methode geeft de tekenreeks jSON weer uit het woordenboek dat door de methode [asdict] wordt geretourneerd. De code is als volgt:
def asjson(self, included_keys: list = None, excluded_keys: list = []) -> str:
# de JSON-string
return json.dumps(self.asdict(included_keys=included_keys, excluded_keys=excluded_keys), ensure_ascii=False)
- regel 1: de parameters van de methode [asjson] zijn dezelfde als die van de methode [asdict];
Hier volgt een voorbeeld (asjson_01) waarin deze methode wordt gebruikt:
# de applicatie wordt geconfigureerd
import config
config = config.configure()
# het syspath is geconfigureerd – we kunnen de imports uitvoeren
from Enseignant import Enseignant
from BaseEntity import BaseEntity
# een docent
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())
# een andere docent
enseignant2 = Enseignant().fromdict({"id": 2, "nom": "abélard", "prénom": "béatrice", "âge": 57})
print(enseignant2.asjson())
print(enseignant2.asjson(included_keys=["_Personne__nom"]))
# een lijst met entiteiten in een entiteit
Enseignant.excluded_keys = []
entity1 = BaseEntity()
enseignants = [enseignant1, enseignant2]
setattr(entity1, "enseignants", enseignants)
print(entity1.asjson())
# een woordenboek met entiteiten binnen een entiteit
matières = {"maths": enseignant1, "français": enseignant2}
setattr(entity1, "matières", matières)
print(entity1.asjson())
De resultaten zijn als volgt:
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
De methode [BaseEntity.__str__] maakt gebruik van de methode [asjson] om de identiteit van het object [BaseEntity] of een daarvan afgeleid object weer te geven:
# toString
def __str__(self) -> str:
return self.asjson()
13.2.4. De methode [BaseEntity.fromjson]
Met de methode [BaseEntity.fromjson] kan een object van het type [BaseEntity] of een daarvan afgeleid object worden geïnitialiseerd op basis van een woordenboek van het type jSON. De code hiervoor is als volgt:
def fromjson(self, json_state: str, silent: bool = False):
# de status van het object wordt bijgewerkt op basis van de tekenreeks jSON
return self.fromdict(json.loads(json_state), silent=silent)
- regel 1: de methode accepteert twee parameters:
- [json_state]: het woordenboek jSON dat zal worden gebruikt om het object [BaseEntity] te initialiseren;
- [silent]: om aan te geven of de aanwezigheid in het woordenboek jSON van een sleutel die niet als eigenschap van het object [BaseEntity] kan worden geaccepteerd, een uitzondering veroorzaakt (silent=False) of gewoon wordt genegeerd (silent=True);
- regel 3: we beginnen met het opbouwen van het Python-woordenboek dat een afspiegeling is van het woordenboek jSON, en vervolgens gebruiken we de methode [fromdict] om het object [BaseEntity] te initialiseren op basis van dit Python-woordenboek;
Hier volgt een voorbeeld (fromjson_01):
# de applicatie wordt geconfigureerd
import config
config = config.configure()
# het syspath is geconfigureerd – de imports kunnen worden uitgevoerd
from Enseignant import Enseignant
import json
# een docent
json1 = json.dumps({"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56})
enseignant1 = Enseignant().fromjson(json1)
enseignant1.show()
- regel 11: de tekenreeks jSON wordt aangemaakt op basis van een woordenboek;
- regel 12: een object [Enseignant] wordt geïnitialiseerd met deze tekenreeks;
- regel 13: de docent wordt weergegeven;
De resultaten zijn als volgt:
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. Het script [main]
Het script [main] geeft een overzicht van de verschillende methoden die aan bod zijn gekomen:
# de applicatie wordt geconfigureerd
import config
config = config.configure()
# het syspath is geconfigureerd – de imports kunnen worden uitgevoerd
from BaseEntity import BaseEntity
from MyException import MyException
# een klas
class ChildEntity(BaseEntity):
# attributen die zijn uitgesloten van de status van de klasse
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})")
# configuratie ChildEntity
ChildEntity.excluded_keys = []
# instantie ChildEntity
child = ChildEntity().fromdict({"att1": 1, "att2": 2})
# let op de namen van de eigenschappen
# dit zijn de namen die worden gebruikt in [excluded_keys] en [included_keys]
print(child.__dict__)
# eigenschappen zonder voorvoegsel van hun klasse
print(child)
# instantie ChildEntity
try:
child = ChildEntity().fromdict({"att1": 1, "att5": 5})
print(child)
except MyException as erreur:
print(erreur)
# instantie ChildEntity
child = ChildEntity().fromdict({"att1": 1, "att2": 2, "att3": 3, "att4": 4})
print(child)
# uitsluitingen van bepaalde sleutels uit de status van de instanties
ChildEntity.excluded_keys = ['att3']
print(child)
# een sleutel wordt expliciet uitgesloten van de weergave
# deze wordt toegevoegd aan de sleutels die globaal op klasseniveau zijn uitgesloten
print(child.asdict(excluded_keys=["_ChildEntity__att1"]))
print(child.asjson(excluded_keys=["att2"]))
# belang van de klasse ten opzichte van het woordenboek
# de klasse kan de geldigheid van de inhoud controleren
try:
child = ChildEntity().fromdict({"att1": 20})
except MyException as erreur:
print(erreur)
# instantie ChildEntity
child1 = ChildEntity().fromdict({"att1": 1, "att2": 2, "att3": 3, "att4": 4})
# instantie ChildEntity die een andere instantie ChildEntity bevat
child2 = ChildEntity().fromdict({"att1": 10, "att2": 20, "att3": 30, "att4": child1})
print(child2)
# included_keys heeft voorrang op excluded_keys, die vervolgens worden genegeerd
ChildEntity.excluded_keys = ['_ChildEntity__att1', 'att2']
print(child.asdict(included_keys=["_ChildEntity__att1", "att3"], excluded_keys=["att3", "att4"]))
De resultaten van de uitvoering zijn als volgt:
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
Let vooral op regel 2 van de resultaten: het is de eigenschap [ChildEntity.__dict__] (regel 38 van de code) die ons in staat stelt de namen te achterhalen van de eigenschappen die in de lijsten [included_keys] en [excluded_keys] moeten worden opgenomen. We merken op, nog steeds in regel 2 van de resultaten, dat, afhankelijk van of de eigenschap binnen de klasse is gedefinieerd via een getter/setter of dat deze is aangemaakt zoals men de sleutel van een woordenboek zou aanmaken, deze al dan niet wordt voorafgegaan door de naam van de klasse [ChildEntity].