Skip to content

13. Die generischen Klassen [BaseEntity] und [MyException]

Wir definieren nun zwei Klassen, die wir im weiteren Verlauf regelmäßig verwenden werden.

Image

13.1. Die Klasse MyException

Die Klasse [MyException] (MyException.py) stellt eine benutzerdefinierte Ausnahmeklasse bereit:


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

Hinweise

  • Zeile 2: Die Klasse [MyException] leitet sich von der vordefinierten Klasse [BaseException] ab;
  • Zeile 4: Der Konstruktor akzeptiert zwei Parameter:
    • [code]: einen ganzzahligen Fehlercode;
    • [message]: eine Fehlermeldung;
  • Zeile 6: Die Fehlermeldung wird an die übergeordnete Klasse übergeben;
  • Zeilen 14–27: Auf das Attribut [code] wird über einen Getter/Setter zugegriffen;
  • Zeilen 23–24: Die Gültigkeit des Attributs [code] wird überprüft: Es muss eine ganze Zahl größer als 0 sein;

13.2. Die Klasse [BaseEntity]

Die Klasse [BaseEntity] wird die übergeordnete Klasse der meisten Klassen sein, die wir erstellen, um Informationen über ein Objekt zu kapseln. Im weiteren Verlauf werden wir hauptsächlich zwei Arten von Klassen verwenden:

  • Klassen, deren einziger Zweck darin besteht, Informationen über ein einzelnes Objekt an einem Ort zu kapseln. Diese verfügen über keine anderen Verhaltensweisen (Methoden) als Getter/Setter und eine Anzeigefunktion (__str__). Wenn N Objekte zu verwalten sind, werden diese Klassen N-mal instanziiert. [BaseEntity] wird die Oberklasse dieser Art von Klassen sein;
  • Klassen, deren Hauptaufgabe darin besteht, Methoden und sehr wenige Informationen zu kapseln. Diese Klassen werden nur einmal instanziiert (Singleton). Ihre Aufgabe ist es, die Algorithmen einer Anwendung zu implementieren;

Die Klasse [BaseEntity] sieht wie folgt aus:

#  imports
import json
import re

from MyException import MyException


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

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

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

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

    #   setter
    @id.setter
    def id(self, id):
        #  id must be an integer >=0
        try:
            id = int(id)
            erreur = id < 0
        except:
            erreur = True
        #  mistake?
        if erreur:
            raise MyException(1, f"L'identifiant d'une entité {self.__class__} doit être un entier >=0")
        else:
            self.__id = id

    def fromdict(self, state: dict, silent=False):
        

    def set_value(self, key: str, value, new_attributes) -> dict:
        

    def asdict(self, included_keys: list = None, excluded_keys: list = []) -> dict:
        

    def asjson(self, excluded_keys: list = []) -> str:
        

    def fromjson(self, json_state: str):
        

Kommentare

  • Der Zweck der Klasse [BaseEntity] besteht darin, die Konvertierung zwischen Objekt und Dictionary sowie zwischen Objekt und JSON zu erleichtern. Sie stellt die folgenden Methoden bereit:
    • [asdict]: gibt ein Dictionary mit den Eigenschaften des Objekts zurück;
    • [fromdict]: Erstellt ein Objekt aus einem Dictionary;
    • [asjson]: gibt die JSON-Zeichenkette des Objekts zurück, ähnlich wie die Funktion [__str__];
    • [fromjson]: erstellt ein Objekt aus dessen JSON-Zeichenkette;
  • Die Klasse [BaseEntity] ist dazu gedacht, abgeleitet und nicht unverändert verwendet zu werden;
  • Zeilen 22–25: Die Klasse [BaseEntity] hat nur eine Eigenschaft, die Ganzzahl [id]. Diese Eigenschaft ist die Kennung des Objekts. In der Praxis ist es oft nützlich, zwischen Instanzen derselben Klasse unterscheiden zu können. Dies erreichen wir mithilfe dieser Eigenschaft, die für jede Instanz eindeutig ist. Darüber hinaus stammen Objekte häufig aus Datenbanken, in denen sie durch einen Primärschlüssel identifiziert werden, typischerweise eine Ganzzahl. In solchen Fällen dient [id] als Primärschlüssel;
  • Zeilen 27–40: Der Setter für die Eigenschaft [id]. Wir überprüfen, ob es sich um eine Ganzzahl >= 0 handelt. Ist dies nicht der Fall, wird eine Ausnahme vom Typ [MyException] ausgelöst (Zeile 39);
  • Zeile 10: [excluded_keys] ist ein Klassenattribut, kein Instanzattribut. Daher schreiben wir [BaseEntity.excluded_keys]. Dieses Klassenattribut ist eine Liste, die die Klassen-Eigenschaften enthält, die nicht an Objekt/Dictionary- und Objekt/JSON-Konvertierungen teilnehmen;
  • Zeilen 12–16: [get_allowed_keys] gibt die Liste der Eigenschaften der Klasse zurück. Bei einer Konvertierung von Dictionary → Object oder JSON → Object werden nur Schlüssel akzeptiert, die in dieser Liste enthalten sind. Jede Klasse, die von der Klasse [BaseEntity] abgeleitet ist, muss diese Liste neu definieren;

Es ist wichtig zu verstehen, dass die Eigenschaften und Funktionen der Klasse [BaseEntity] für von [BaseEntity] abgeleitete Klassen zugänglich sind. Dies ist der entscheidende Punkt, den es zu begreifen gilt.

Wir werden nun den Code der Klasse [BaseEntity] im Detail untersuchen. Er ist recht fortgeschritten. Anfänger können sich einfach mit der Beschreibung der Rolle jeder Funktion begnügen, ohne sich mit dem Code selbst auseinanderzusetzen.

13.2.1. Die Methode [BaseEntity.fromdict]

13.2.1.1. Definition

Mit der Methode [fromdict] können Sie ein [BaseEntity]-Objekt oder ein davon abgeleitetes Objekt aus einem Wörterbuch initialisieren:

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

Kommentare

  • Zeile 1: Die Funktion erhält das Wörterbuch [state] als Parameter, aus dem das aktuelle Objekt initialisiert wird;
  • Zeile 4: Wir rufen die statische Funktion [get_allowed_keys] der Klasse auf, die die Funktion [fromdict] aufgerufen hat. Wenn es sich um eine von [BaseEntity] abgeleitete Klasse handelt und diese abgeleitete Klasse die statische Funktion [get_allowed_keys] neu definiert hat, wird die Funktion [get_allowed_keys] aufgerufen. Jede abgeleitete Klasse definiert diese statische Funktion neu, um ihre Eigenschaften zu deklarieren;
  • Zeile 6: Wir durchlaufen die Schlüssel und Werte des [state]-Wörterbuchs;
  • Zeile 8: Wenn der Schlüssel [key] nicht zu den Eigenschaften der Klasse gehört, dann gilt entweder:
    • wird er ignoriert;
    • Es wird eine Ausnahme ausgelöst (Zeile 10). Der Entwickler legt seine Präferenz fest, indem er den entsprechenden Parameter [silent] übergibt (Zeile 1). Der Standardwert von [silent] bewirkt, dass eine Ausnahme ausgelöst wird, wenn versucht wird, das Objekt mit einer Eigenschaft zu initialisieren, über die es nicht verfügt;
  • Zeile 14: Befindet sich der Schlüssel unter den Eigenschaften des Objekts, wird er dem Objekt [self] mithilfe der vordefinierten Funktion [setattr] zugewiesen;
  • Zeile 16: Die Funktion gibt das initialisierte Objekt zurück;

13.2.1.2. Beispiele

Image

13.2.1.2.1. Die Klasse [Utils]

Die Klasse [Utils] (Utils.py) sieht wie folgt aus:

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

In den Zeilen 3–11 wird eine statische Methode definiert, die den booleschen Wert „true“ zurückgibt, wenn ihr Parameter [str] eine nicht leere Zeichenkette ist;

13.2.1.2.2. Die Klasse [Person]

Die Klasse [Person] (Person.py) leitet sich von der Klasse [BaseEntity] ab:

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


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

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

    #  getters
    @property
    def prénom(self) -> str:
        return self.__prénom

    @property
    def nom(self) -> str:
        return self.__nom

    @property
    def âge(self) -> int:
        return self.__âge

    #  setters
    @prénom.setter
    def prénom(self, prénom: str):
        #  first name must be non-empty
        if Utils.is_string_ok(prénom):
            self.__prénom = prénom.strip()
        else:
            raise MyException(11, "Le prénom doit être une chaîne de caractères non vide")

    @nom.setter
    def nom(self, nom: str):
        #  first name must be non-empty
        if Utils.is_string_ok(nom):
            self.__nom = nom.strip()
        else:
            raise MyException(12, "Le nom doit être une chaîne de caractères non vide")

    @âge.setter
    def âge(self, âge: int):
        #  age must be an integer >=0
        erreur = False
        if isinstance(âge, int):
            if âge >= 0:
                self.__âge = âge
            else:
                erreur = True
        else:
            erreur = True
        #  mistake?
        if erreur:
            raise MyException(13, "L'âge doit être un entier >=0")
  • Zeile 8: Die Klasse [Person] leitet sich von der Klasse [BaseEntity] ab;
  • Zeilen 8–65: Wir haben den Großteil der zuvor behandelten Klasse [Person] beibehalten. Die Unterschiede sind wie folgt:
    • Die Klasse hat keinen Konstruktor mehr;
    • die Klasse verwendet die Ausnahme [MyException], z. B. in Zeile 65;
    • sie verfügt über eine statische Methode, [get_allowed_keys], Zeilen 17–20, die die Liste ihrer Eigenschaften definiert. Die für die Klasse [Person] spezifischen Eigenschaften werden zu denen der übergeordneten Klasse [BaseEntity] hinzugefügt;
    • sie verfügt über eine statische Liste [excluded_keys], auf die wir später zurückkommen werden;
13.2.1.2.3. Die Klasse [Teacher]

Die Klasse [Teacher] (Teacher.py) leitet sich von der Klasse [Person] ab:

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


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

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

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

    @discipline.setter
    def discipline(self, discipline: str):
        #  the discipline must be a non-empty string
        if Utils.is_string_ok(discipline):
            self.__discipline = discipline
        else:
            raise MyException(21, "La discipline doit être une chaîne de caractères non vide")

    #  show method
    def show(self):
        print(f"Enseignant[{self.id}, {self.prénom}, {self.nom}, {self.âge}]")
  • Zeile 8: Die Klasse [Lehrer] erweitert (oder leitet sich ab von) der Klasse [Person];
  • Zeilen 18–21: Definieren die Liste der Eigenschaften für die Klasse;
  • Zeilen 37–38: Die Methode [show] zeigt die Identität des Lehrers an;
13.2.1.2.4. Die [config]-Konfiguration

Die Beispielskripte verwenden die folgende [config]-Konfiguration:

def configure():
    import os

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

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

    #  update syspath
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    #  we return the configuration
    return {}
  • Zeilen 8–10: die Verzeichnisse, die die Abhängigkeiten des Projekts enthalten;
  • Zeilen 14–15: Der Python-Pfad wird aufgebaut;
  • Zeile 18: gibt ein leeres Wörterbuch zurück (es gibt keine anderen Konfigurationen außer dem syspath);
13.2.1.2.5. Das Skript [fromdict_01]

Das Skript [fromdict_01] lautet wie folgt:

#  configure the application
import config

config = config.configure()

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

#  a teacher
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56})
enseignant1.show()
  • Zeile 10: Wir erstellen ein [Lehrer]-Objekt aus einem Wörterbuch. Dazu verwenden wir den Standardkonstruktor der Klasse, um ein [Lehrer]-Objekt zu erstellen, auf das wir die [fromdict]-Methode anwenden. Es ist wichtig zu verstehen, dass hier die [fromdict]-Methode der übergeordneten Klasse [BaseEntity] ausgeführt wird. Tatsächlich:
    • wird zunächst in der Klasse [Teacher] nach der Methode [fromdict] gesucht. Da diese dort nicht vorhanden ist,
    • dann wird in der übergeordneten Klasse [Person] danach gesucht. Dort existiert sie nicht;
    • dann wird in der übergeordneten Klasse [BaseEntity] danach gesucht. Dort existiert sie;
  • Zeile 11: Das [Teacher]-Objekt wird angezeigt;

Die Ergebnisse lauten wie folgt:


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. Das Skript [fromdict_02]

Das Skript [fromdict_02] lautet wie folgt:

#  configure the application
import config

config = config.configure()

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

#  a teacher
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "", "âge": 56})
enseignant1.show()
  • Zeile 10: Wir erstellen einen Lehrer mit einem leeren Vornamen. Dies sollte eine Ausnahme auslösen, da die Klasse [Person] keine leeren Vornamen akzeptiert. Dieses Beispiel veranschaulicht den Unterschied zwischen einem Wörterbuch und einem Objekt. Letzteres kann seine Eigenschaften validieren, während das Wörterbuch dies nicht kann;

Die Ergebnisse lauten wie folgt:


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. Das Skript [fromdict_03]

Das Skript [fromdict_03] lautet wie folgt:

#  configure the application
import config

config = config.configure()

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

#  a teacher
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "albert", "âge": 56, "sexe": "M"})
enseignant1.show()
  • Zeile 10: Wir erstellen einen Lehrer aus einem Wörterbuch, das einen Schlüssel (Geschlecht) enthält, der nicht zur Klasse [Lehrer] gehört. Es sollte dann eine Ausnahme ausgelöst werden;

Die Ergebnisse lauten wie folgt:


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. Das Skript [fromdict_04]

Das Skript [fromdict_04] ist eine Kopie von [fromdict_03] mit einem kleinen Unterschied:

#  configure the application
import config

config = config.configure()

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

#  a teacher
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "albert", "âge": 56, "sexe": "M"}, silent=True)
enseignant1.show()
  • Zeile 10: Wir haben den Parameter [silent=True] verwendet, um anzugeben, dass ein Schlüssel im Wörterbuch, der keine Eigenschaft der Klasse [Teacher] ist, einfach ignoriert werden soll. In diesem Fall wird keine Ausnahme ausgelöst;

Die Ergebnisse lauten wie folgt:


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. Die Methode [BaseEntity.asdict]

13.2.2.1. Definition

Die Methode [BaseEntity.asdict] gibt ein Wörterbuch zurück, dessen Schlüssel die Eigenschaften des Objekts sind:


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

Kommentare

  • Zeile 1: Die Funktion [asdict] gibt das Wörterbuch der Eigenschaften des Objekts zurück;
  • Zeile 1: [included_keys]: die Liste der Schlüssel, die in das Wörterbuch aufgenommen werden sollen;
  • Zeile 1: [excluded_keys]: die Liste der Schlüssel, die aus dem Wörterbuch ausgeschlossen werden sollen;
  • Zeile 3: Die Eigenschaft [self.__dict__] gibt das Eigenschaftswörterbuch des Objekts zurück. Die Eigenschaftsnamen sind die Schlüssel und ihre Werte sind die Werte des Wörterbuchs. Ein Objekt kann Verweise auf andere Objekte enthalten. In diesem Fall wird den Eigenschaftsnamen der Name der Klasse vorangestellt, zu der sie gehören. Das ist etwas, was wir nicht wollen. Wir wollen die Eigenschaften ohne ihr Präfix;
  • Zeile 3: Es ist wichtig zu verstehen, dass, wenn die Funktion [asdict] innerhalb einer von [BaseEntity] abgeleiteten Klasse ausgeführt wird, die Eigenschaft [self.__dict__] das Eigenschaftswörterbuch für das abgeleitete Objekt zurückgibt;
  • Zeile 5: das Wörterbuch, das wir erstellen werden;
  • Zeile 7: Wir durchlaufen die Werte von [self.__dict__] in der Form (Schlüssel, Wert);
  • Zeile 9: Wenn der aktuelle Schlüssel zur Liste der einzufügenden Schlüssel gehört, wird er mithilfe der Funktion [set_value], die wir in Kürze beschreiben werden, zum Wörterbuch [new_attributes] hinzugefügt;
  • Zeile 12: Wenn der Parameter [included_keys] nicht vorhanden ist, wird der Parameter [excluded_keys] verwendet. Wenn die Eigenschaft nicht zu den auszuschließenden Eigenschaften gehört, wird sie dem Wörterbuch [new_attributes] hinzugefügt;
  • Zeile 12: Es gibt mehrere Möglichkeiten, eine Eigenschaft aus dem Wörterbuch auszuschließen:
    • sie wurde auf der Ebene der Klassenattribute [excluded_keys] definiert;
    • sie wurde in der Liste [excluded_keys] definiert, die an die Funktion [asdict] übergeben wurde;
    • der Parameter [included_keys] ist vorhanden und enthält die Eigenschaft nicht;
  • Zeile 15: Wir geben das Wörterbuch [new_attributes] zurück

Die Funktion [set_value] in den Zeilen 10 und 13 lautet wie folgt:

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

Kommentare

  • Zeile 4: Prüfe, ob der Schlüssel die Form __Class_key hat. Diese Form nimmt er an, wenn er zu einem Objekt gehört, das im Hauptobjekt enthalten ist. In diesem Fall wollen wir nur die Zeichenkette [key] behalten;
  • Zeile 7: Wir behalten nur die Zeichenfolge, die auf die letzten beiden unterstrichenen Zeichen der Zeichenfolge folgt;
  • Zeilen 8–10: Wenn der Schlüssel nicht die Form __Class_key hat, behalten wir ihn unverändert bei;
  • Zeilen 11–14: Der mit dem Schlüssel [newkey] verknüpfte Wert wird durch die statische Methode [BaseEntity.check_value] berechnet;

Die statische Methode [BaseEntity.check_value] lautet wie folgt:

    @staticmethod
    def check_value(value):
        #  the value can be of type BaseEntity, list, dict or a simple type
        #  is value an instance of BaseEntity?
        if isinstance(value, BaseEntity):
            value2 = value.asdict()
        #  is value of type list
        elif isinstance(value, list):
            value2 = BaseEntity.list2list(value)
        #  is value a dict type?
        elif isinstance(value, dict):
            value2 = BaseEntity.dict2dict(value)
        #  value is a simple type
        else:
            value2 = value
        #  we return the result
        return value2
  • Zeile 1: Die Methode [check_value] ist statisch (eine Klassenmethode, keine Instanzmethode). Sie nimmt als Parameter den Wert entgegen, der einem Schlüssel im Wörterbuch zugeordnet werden soll:
    • Zeile 17: Wenn dieser Wert ein einfacher Typ ist, bleibt er unverändert;
    • Zeilen 5–6: Ist dieser Wert vom Typ BaseEntity, wird er durch sein Wörterbuch ersetzt. Dies führt zu einem rekursiven Aufruf;
    • Zeilen 8–9: Ist dieser Wert eine Liste, wird er durch den Wert [BaseEntity.list2list] ersetzt;
    • Zeilen 11–12: Wenn dieser Wert ein Wörterbuch ist, wird er durch den Wert [BaseEntity.dict2dict] ersetzt;

Die statische Methode [BaseEntity.list2list] lautet wie folgt:

1
2
3
4
5
6
7
8
    @staticmethod
    def list2list(liste: list) -> list:
        #  inspect list items
        newlist = []
        for value in liste:
            newlist.append(BaseEntity.check_value(value))
        #  return the new list
        return newlist
  • Zeile 2: Die Methode erhält eine Liste und gibt eine Liste zurück;
  • Zeilen 5–6: Jeder Wert in der als Parameter übergebenen Liste wird durch den Wert ersetzt, der von der statischen Methode [BaseEntity.check_value] zurückgegeben wird. Es handelt sich also um einen rekursiven Aufruf. Die statische Methode [BaseEntity.check_value] wird so lange aufgerufen, bis ihr Parameter [value] ein einfacher Typ ist (kein BaseEntity-, Listen- oder Dict-Typ);

Die statische Methode [BaseEntity.dict2dict] lautet wie folgt:

1
2
3
4
5
6
7
8
    @staticmethod
    def dict2dict(dictionary: dict) -> dict:
        #  inspect dictionary items
        newdict = {}
        for key, value in dictionary.items():
            newdict[key] = BaseEntity.check_value(value)
        #  the new dictionary is returned
        return newdict
  • Zeile 2: Die Methode erhält ein Wörterbuch und gibt ein Wörterbuch zurück;
  • Zeilen 5–6: Jeder Wert im als Parameter übergebenen Wörterbuch wird durch den Wert ersetzt, der von der statischen Methode [BaseEntity.check_value] zurückgegeben wird. Es handelt sich also um einen rekursiven Aufruf. Die statische Methode [BaseEntity.check_value] wird so lange aufgerufen, bis ihr Parameter [value] ein einfacher Typ ist (keine BaseEntity, Liste oder kein Wörterbuch);

13.2.2.2. Beispiele

Das Skript [asdict_01] veranschaulicht verschiedene Anwendungsmöglichkeiten der Methode [asdict]:

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

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

#  a teacher
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56})
dict1 = enseignant1.asdict()
print(type(dict1))
print(enseignant1.__dict__)
print(dict1)
print(enseignant1.asdict(excluded_keys=["_Personne__âge"]))
Enseignant.excluded_keys = ["_Personne__prénom"]
print(enseignant1)
#  another teacher
enseignant2 = Enseignant().fromdict({"id": 2, "nom": "abélard", "prénom": "béatrice", "âge": 57})
print(enseignant2.asdict())
print(enseignant2.asdict(included_keys=["_Personne__nom"]))
#  a list of entities within an entity
Enseignant.excluded_keys = []
entity1 = BaseEntity()
enseignants = [enseignant1, enseignant2]
setattr(entity1, "enseignants", enseignants)
print(entity1.asdict())
#  a dictionary of entities within an entity
matières = {"maths": enseignant1, "français": enseignant2}
setattr(entity1, "matières", matières)
print(entity1.asdict())

Die Ergebnisse der Ausführung lauten wie folgt:


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
  • Zeile 4 veranschaulicht den Vorteil der [asdict]-Methode gegenüber der Verwendung der [__dict__]-Eigenschaft. Den Eigenschaften wird ihr Klassenpräfix entfernt. Dadurch lassen sie sich leichter anzeigen;
  • Es gibt mehrere Möglichkeiten, die [asdict]-Methode zu verwenden:
    • Wenn Sie alle Eigenschaften anzeigen möchten: Verwenden Sie die Methode [asdict] ohne Parameter;
    • Wenn Sie nur bestimmte Eigenschaften wünschen:
      • Es gibt mehr Eigenschaften, die einbezogen werden sollen, als ausgeschlossen werden sollen: Verwenden Sie den einzelnen Parameter [excluded_keys];
      • Es gibt weniger Eigenschaften, die einbezogen werden sollen als ausgeschlossen werden sollen: Wir verwenden nur den Parameter [included_keys];

13.2.3. Die Methode [BaseEntity.asjson]

Diese Methode gibt die JSON-Zeichenkette eines [BaseEntity]-Objekts oder einer davon abgeleiteten Klasse zurück. Sie gibt die JSON-Zeichenkette des von der [asdict]-Methode zurückgegebenen Wörterbuchs aus. Der Code lautet wie folgt:

1
2
3
def asjson(self, included_keys: list = None, excluded_keys: list = []) -> str:
        #  the json string
        return json.dumps(self.asdict(included_keys=included_keys, excluded_keys=excluded_keys), ensure_ascii=False)
  • Zeile 1: Die Parameter der Methode [asjson] sind die der Methode [asdict];

Hier ist ein Beispiel (asjson_01) für die Verwendung dieser Methode:

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

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

#  a teacher
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56})
print(type(enseignant1.asjson()))
print(enseignant1.asjson(excluded_keys=["_Personne__âge"]))
Enseignant.excluded_keys = ["_Personne__prénom"]
print(enseignant1.asjson())
#  another teacher
enseignant2 = Enseignant().fromdict({"id": 2, "nom": "abélard", "prénom": "béatrice", "âge": 57})
print(enseignant2.asjson())
print(enseignant2.asjson(included_keys=["_Personne__nom"]))
#  a list of entities within an entity
Enseignant.excluded_keys = []
entity1 = BaseEntity()
enseignants = [enseignant1, enseignant2]
setattr(entity1, "enseignants", enseignants)
print(entity1.asjson())
#  a dictionary of entities within an entity
matières = {"maths": enseignant1, "français": enseignant2}
setattr(entity1, "matières", matières)
print(entity1.asjson())

Die Ergebnisse lauten wie folgt:


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

Die Methode [BaseEntity.__str__] verwendet die Methode [asjson], um die Identität des Objekts [BaseEntity] oder seiner abgeleiteten Objekte anzuzeigen:


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

13.2.4. Die Methode [BaseEntity.fromjson]

Die Methode [BaseEntity.fromjson] ermöglicht es Ihnen, ein Objekt vom Typ [BaseEntity] oder einem abgeleiteten Typ aus einem JSON-Dictionary zu initialisieren. Der Code lautet wie folgt:

1
2
3
def fromjson(self, json_state: str, silent: bool = False):
        #  update object status from jSON string
        return self.fromdict(json.loads(json_state), silent=silent)
  • Zeile 1: Die Methode nimmt zwei Parameter entgegen:
    • [json_state]: das JSON-Dictionary, das zur Initialisierung des [BaseEntity]-Objekts verwendet wird;
    • [silent]: gibt an, ob das Vorhandensein eines Schlüssels im JSON-Wörterbuch, der nicht als Eigenschaft des [BaseEntity]-Objekts akzeptiert werden kann, eine Ausnahme auslöst (silent=False) oder einfach ignoriert wird (silent=True);
  • Zeile 3: Wir beginnen mit der Erstellung des Python-Wörterbuchs, das das JSON-Wörterbuch repräsentiert, und verwenden dann die Methode [fromdict], um das [BaseEntity]-Objekt aus diesem Python-Wörterbuch zu initialisieren;

Hier ist ein Beispiel (fromjson_01):

#  configure the application
import config

config = config.configure()

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

#  a teacher
json1 = json.dumps({"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56})
enseignant1 = Enseignant().fromjson(json1)
enseignant1.show()
  • Zeile 11: Wir erstellen die JSON-Zeichenkette eines Wörterbuchs;
  • Zeile 12: Ein [Enseignant]-Objekt wird mit dieser Zeichenfolge initialisiert;
  • Zeile 13: Der Lehrer wird angezeigt;

Die Ergebnisse lauten wie folgt:


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. Das Skript [main]

Das Skript [main] fasst die verschiedenen behandelten Methoden zusammen:

#  configure the application
import config

config = config.configure()

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


#  a class
class ChildEntity(BaseEntity):
    #  attributes excluded from class state
    excluded_keys = []

    @staticmethod
    def get_allowed_keys():
        return ["att1", "att2", "att3", "att4"]

    @property
    def att1(self) -> int:
        return self.__att1

    @att1.setter
    def att1(self, value: int):
        if 10 >= value >= 1:
            self.__att1 = value
        else:
            raise MyException(1, f"L'attribut [att1] attend une valeur dans l'intervalle [1,10] ({value})")


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

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

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

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

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

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

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

#  included_keys has priority over excluded_keys which are then ignored
ChildEntity.excluded_keys = ['_ChildEntity__att1', 'att2']
print(child.asdict(included_keys=["_ChildEntity__att1", "att3"], excluded_keys=["att3", "att4"]))

Die Ergebnisse der Ausführung lauten wie folgt:


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

Beachten Sie Zeile 2 der Ergebnisse: Es ist die Eigenschaft [ChildEntity.__dict__] (Zeile 38 des Codes), die es uns ermöglicht, die Namen der Eigenschaften zu ermitteln, die in die Listen [included_keys] und [excluded_keys] aufgenommen werden sollen. Beachten Sie, ebenfalls in Zeile 2 der Ergebnisse, dass der Eigenschaftsname je nachdem, ob die Eigenschaft innerhalb der Klasse über einen Getter/Setter definiert oder wie ein Dictionary-Schlüssel erstellt wurde, mit dem Klassennamen [ChildEntity] vorangestellt sein kann oder nicht.