Skip to content

13. Le classi generiche [BaseEntity] e [MyException]

Definiamo ora due classi che utilizzeremo regolarmente in seguito.

Image

13.1. La classe MyException

La classe [MyException] (MyException.py) fornisce una classe di eccezioni proprietaria:


# una classe di eccezione proprietaria derivata da [BaseException]
class MyException(BaseException):
    # costruttore
    def __init__(self: object, code: int, message: str):
        # classe padre
        BaseException.__init__(self, message)
        # codice di errore
        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):
        # il codice di errore deve essere un numero intero positivo
        if isinstance(code, int) and code > 0:
            self.__code = code
        else:
            # eccezione
            raise BaseException(f"code erreur {code} incorrect")

Note

  • riga 2: la classe [MyException] deriva dalla classe predefinita [BaseException];
  • riga 4: il costruttore accetta due parametri:
    • [code]: un codice di errore intero;
    • [message]: un messaggio di errore;
  • riga 6: si passa il messaggio di errore alla classe padre;
  • righe 14-27: l'attributo [code] viene gestito tramite un getter/setter;
  • righe 23-24: viene verificata la validità dell'attributo [code]: deve essere un numero intero >0;

13.2. La classe [BaseEntity]

La classe [BaseEntity] sarà la classe padre della maggior parte delle classi che creeremo per incapsulare le informazioni relative a un oggetto. Di seguito utilizzeremo principalmente due tipi di classi:

  • classi il cui unico scopo è incapsulare in un unico posto le informazioni relative a uno stesso oggetto. Queste non avranno comportamenti (metodi) diversi dai getter/setter e da una funzione di visualizzazione (__str__). Se ci sono N oggetti da gestire, queste classi vengono istanziate N volte. [BaseEntity] sarà la classe padre di questo tipo di classi;
  • classi il cui ruolo principale è quello di incapsulare metodi e pochissime informazioni. Queste classi saranno istanziate una sola volta (singleton). Il loro ruolo è quello di implementare gli algoritmi di un’applicazione;

La classe [BaseEntity] è la seguente:


# importazioni
import json
import re

from MyException import MyException


class BaseEntity(object):
    # proprietà escluse dallo stato della classe
    excluded_keys = []

    # proprietà della classe
    @staticmethod
    def get_allowed_keys() -> list:
        # id: identificatore dell'oggetto
        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):
        # l'ID deve essere un numero intero >=0
        try:
            id = int(id)
            erreur = id < 0
        except:
            erreur = True
        # errore?
        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):
        

Commenti

  • l’obiettivo della classe [BaseEntity] è facilitare le conversioni Oggetto / Dizionario e Oggetto / jSON. Vengono quindi offerti i seguenti metodi:
    • [asdict]: restituisce il dizionario delle proprietà dell’oggetto;
    • [fromdict]: crea un oggetto a partire da un dizionario;
    • [asjson]: restituisce la stringa jSON dell’oggetto, come fa la funzione [__str__];
    • [fromjson]: crea un oggetto a partire dalla sua stringa jSON;
  • la classe [BaseEntity] è destinata a essere derivata e non a essere utilizzata così com'è;
  • righe 22-25: la classe [BaseEntity] ha una sola proprietà, il numero intero [id]. Questa proprietà è l’identificatore dell’oggetto. In pratica, è spesso utile poter distinguere le istanze di una stessa classe. Lo faremo con questa proprietà, unica per ogni istanza. Inoltre, gli oggetti provengono spesso da database in cui sono identificati da una chiave primaria, solitamente un numero intero. In questi casi, [id] sarà la chiave primaria;
  • righe 27-40: il setter della proprietà [id]. Si verifica che sia un numero intero >=0. In caso contrario, viene generata un’eccezione di tipo [MyException] (riga 39);
  • riga 10: [excluded_keys] è un attributo di classe e non di istanza. Pertanto si scriverà [BaseEntity.excluded_keys]. Questo attributo di classe è un elenco contenente le proprietà di classe che non partecipano alle conversioni Oggetto / Dizionario e Oggetto / jSON;
  • righe 12-16: [get_allowed_keys] restituisce l'elenco delle proprietà della classe. In una conversione Dizionario -> Oggetto o jSON -> Oggetto, saranno accettate solo le chiavi presenti in questo elenco. Ogni classe derivata dalla classe [BaseEntity] dovrà ridefinire questo elenco;

È importante comprendere che le proprietà e le funzioni della classe [BaseEntity] sono accessibili alle classi derivate da [BaseEntity]. Questo è il punto fondamentale da comprendere.

Analizzeremo in dettaglio il codice della classe [BaseEntity]. Si tratta di un codice piuttosto avanzato. Il lettore alle prime armi potrà limitarsi a leggere la descrizione di ciascuna funzione senza soffermarsi sul codice stesso.

13.2.1. Il metodo [BaseEntity.fromdict]

13.2.1.1. Définition

Il metodo [fromdict] consente di inizializzare un oggetto [BaseEntity] o derivato da un dizionario:


def fromdict(self, state: dict, silent=False):
        # si sta aggiornando l'oggetto
        # chiavi autorizzate
        allowed_keys = self.__class__.get_allowed_keys()
        # scorre le chiavi di stato
        for key, value in state.items():
            # la chiave è autorizzata?
            if key not in allowed_keys:
                if not silent:
                    raise MyException(2, f"la clé {key} n'est pas autorisée")
            else:
                # si tenta di assegnare il valore alla chiave
                # si lascia che l'eventuale eccezione venga segnalata
                setattr(self, key, value)
        # si restituisce l'oggetto
        return self

Commenti

  • riga 1: la funzione riceve come parametro il dizionario [state] a partire dal quale verrà inizializzato l’oggetto corrente;
  • riga 4: viene chiamata la funzione statica [get_allowed_keys] della classe che ha chiamato la funzione [fromdict]. Se si ha a che fare con una classe derivata da [BaseEntity] e tale classe derivata ha ridefinito la funzione statica [get_allowed_keys], allora viene chiamata la funzione [get_allowed_keys]. Ogni classe derivata ridefinisce questa funzione statica per dichiararvi le proprie proprietà;
  • riga 6: si percorrono le chiavi e i valori del dizionario [state];
  • riga 8: se la chiave [key] non fa parte delle proprietà della classe, allora:
    • la si ignora;
    • si genera un'eccezione (riga 10). Lo sviluppatore specifica ciò che desidera passando il parametro corretto [silent] (riga 1). Il valore predefinito di [silent] fa sì che venga generata un'eccezione se si tenta di inizializzare l'oggetto con una proprietà che non possiede;
  • riga 14: se la chiave fa parte delle proprietà dell’oggetto, allora viene assegnata all’oggetto [self] utilizzando la funzione predefinita [setattr];
  • riga 16: la funzione restituisce l’oggetto inizializzato;

13.2.1.2. Exemples

Image

13.2.1.2.1. La classe [Utils]

La classe [Utils] (Utils.py) è la seguente:


class Utils:
    # metodo statico
    @staticmethod
    def is_string_ok(string: str) -> bool:
        # string è una stringa
        erreur = not isinstance(string, str)
        if not erreur:
            # la stringa è vuota?
            erreur = string.strip() == ''
        # risultato
        return not erreur

Alle righe 3-11 definisce un metodo statico che restituisce un valore booleano vero se il suo parametro [str] è una stringa non vuota;

13.2.1.2.2. La classe [Personne]

La classe [Personne] (Personne.py) deriva dalla classe [BaseEntity]:


# importazioni
from BaseEntity import BaseEntity
from MyException import MyException
from Utils import Utils


# classe Persona
class Personne(BaseEntity):
    # proprietà escluse dallo stato della classe
    excluded_keys = []

    # proprietà della classe
    # id: identificativo della persona
    # nome: nome della persona
    # cognome: cognome della persona
    # età: età della persona
    @staticmethod
    def get_allowed_keys() -> list:
        # id: identificativo dell'oggetto
        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

    # setter
    @prénom.setter
    def prénom(self, prénom: str):
        # il nome deve essere diverso da vuoto
        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):
        # il nome deve essere diverso da vuoto
        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):
        # l'età deve essere un numero intero >=0
        erreur = False
        if isinstance(âge, int):
            if âge >= 0:
                self.__âge = âge
            else:
                erreur = True
        else:
            erreur = True
        # errore?
        if erreur:
            raise MyException(13, "L'âge doit être un entier >=0")
  • riga 8: la classe [Personne] deriva dalla classe [BaseEntity];
  • righe 8-65: è stata mantenuta la parte essenziale della classe [Personne] già incontrata. Le differenze sono le seguenti:
    • la classe non ha più un costruttore;
    • la classe utilizza l’eccezione [MyException], come nell’esempio alla riga 65;
    • dispone di un metodo statico, [get_allowed_keys], righe 17-20, che definisce l'elenco delle sue proprietà. Le proprietà specifiche della classe [Personne] vengono aggiunte a quelle della classe padre [BaseEntity];
    • ha un elenco statico [excluded_keys] su cui torneremo;
13.2.1.2.3. La classe [Enseignant]

La classe [Enseignant] (Enseignant.py) deriva dalla classe [Personne]:


# importazioni
from MyException import MyException
from Personne import Personne
from Utils import Utils


# classe Insegnante
class Enseignant(Personne):
    # proprietà escluse dallo stato della classe
    excluded_keys = []

    # proprietà della classe
    # id: identificativo della persona
    # nome: nome della persona
    # cognome: cognome della persona
    # età: età della persona
    # materia: materia insegnata
    @staticmethod
    def get_allowed_keys() -> list:
        # ID: identificativo dell'oggetto
        return Personne.get_allowed_keys() + ["discipline"]

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

    @discipline.setter
    def discipline(self, discipline: str):
        # la disciplina deve essere una stringa non vuota
        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")

    # metodo show
    def show(self):
        print(f"Enseignant[{self.id}, {self.prénom}, {self.nom}, {self.âge}]")
  • riga 8: la classe [Enseignant] estende (o deriva) dalla classe [Personne];
  • righe 18-21: definiscono l'elenco delle proprietà della classe;
  • righe 37-38: il metodo [show] visualizza l’identità del docente;
13.2.1.2.4. La configurazione [config]

Gli script di esempio utilizzano la seguente configurazione [config]:


def configure():
    import os

    # cartella del file di configurazione
    script_dir = os.path.dirname(os.path.abspath(__file__))

    # percorsi assoluti delle cartelle da inserire nel syspath
    absolute_dependencies = [
        # la classe BaseEntity
        f"{script_dir}/entities",
    ]

    # aggiornamento del syspath
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # si rende la configurazione
    return {}
  • righe 8-10: le cartelle contenenti le dipendenze del progetto;
  • righe 14-15: viene costruito il Python Path;
  • riga 18: viene restituito un dizionario vuoto (non ci sono altre configurazioni oltre a quella del syspath);
13.2.1.2.5. Lo script [fromdict_01]

Lo script [fromdict_01] è il seguente:


# si configura l'applicazione
import config

config = config.configure()

# il syspath è configurato - è possibile eseguire le importazioni
from Enseignant import Enseignant

# un docente
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56})
enseignant1.show()
  • riga 10: si crea un oggetto [Enseignant] a partire da un dizionario. A tal fine, si utilizza il costruttore predefinito della classe per creare un oggetto [Enseignant] al quale si applica il metodo [fromdict]. È importante comprendere che in questo caso il metodo [fromdict] eseguito è quello della classe padre [BaseEntity]. Infatti:
    • il metodo [fromdict] viene innanzitutto cercato nella classe [Enseignant]. Non esiste;
    • viene quindi cercato nella classe padre [Personne]. Non esiste;
    • viene quindi cercato nella classe padre [BaseEntity]. Esiste;
  • riga 11: viene visualizzato l’oggetto [Enseignant];

I risultati sono i seguenti:


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. Lo script [fromdict_02]

Lo script [fromdict_02] è il seguente:


# si configura l'applicazione
import config

config = config.configure()

# il syspath è configurato - è possibile eseguire le importazioni
from Enseignant import Enseignant

# un docente
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "", "âge": 56})
enseignant1.show()
  • riga 10: si crea un insegnante con il nome vuoto. Ciò dovrebbe generare un'eccezione poiché la classe [Personne] non accetta nomi vuoti. Questo esempio mostra la differenza tra un dizionario e un oggetto. Quest'ultimo può verificare la validità delle proprie proprietà, mentre il dizionario no;

I risultati sono i seguenti:


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. Lo script [fromdict_03]

Lo script [fromdict_03] è il seguente:


# si configura l'applicazione
import config

config = config.configure()

# il syspath è configurato - è possibile eseguire le importazioni
from Enseignant import Enseignant

# un docente
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "albert", "âge": 56, "sexe": "M"})
enseignant1.show()
  • riga 10: si crea un insegnante a partire da un dizionario contenente una chiave (sesso) che non appartiene alla classe [Enseignant]. A questo punto dovrebbe essere generata un'eccezione;

I risultati sono i seguenti:


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. Lo script [fromdict_04]

Lo script [fromdict_04] è una copia di [fromdict_03] con una sola differenza:


# si configura l'applicazione
import config

config = config.configure()

# il syspath è configurato - è possibile eseguire le importazioni
from Enseignant import Enseignant

# un docente
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "albert", "âge": 56, "sexe": "M"}, silent=True)
enseignant1.show()
  • riga 10: è stato utilizzato il parametro [silent=True] per indicare che, se una chiave del dizionario non è una proprietà della classe [Enseignant], deve semplicemente essere ignorata. In questo caso, non verrà generata alcuna eccezione;

I risultati sono i seguenti:


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. Il metodo [BaseEntity.asdict]

13.2.2.1. Définition

Il metodo [BaseEntity.asdict] restituisce un dizionario le cui chiavi sono le proprietà dell’oggetto:


    def asdict(self, included_keys: list = None, excluded_keys: list =[]) -> dict:
        # attributi dell'oggetto
        attributes = self.__dict__
        # i nuovi attributi
        new_attributes = {}
        # si scorrono gli attributi
        for key, value in attributes.items():
            # se la chiave è esplicitamente richiesta
            if included_keys and key in included_keys:
                self.set_value(key, value, new_attributes)
            # altrimenti, se la chiave non è esclusa
            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)
        # si restituisce il dizionario degli attributi
        return new_attributes

Commenti

  • riga 1: la funzione [asdict] restituisce il dizionario delle proprietà dell'oggetto;
  • riga 1: [included_keys]: l'elenco delle chiavi da includere nel dizionario;
  • riga 1: [excluded_keys]: l'elenco delle chiavi da escludere dal dizionario;
  • riga 3: la proprietà [self.__dict__] restituisce il dizionario delle proprietà dell’oggetto. I nomi delle proprietà sono le chiavi e i loro valori sono i valori del dizionario. Un oggetto può contenere riferimenti ad altri oggetti. I nomi delle proprietà sono quindi preceduti dal nome della classe a cui appartengono. Questo è qualcosa che non vogliamo. Vogliamo le proprietà senza il loro prefisso;
  • riga 3: occorre comprendere qui che se la funzione [asdict] viene eseguita all’interno di una classe derivata da [BaseEntity], la proprietà [self.__dict__] restituisce il dizionario delle proprietà dell’oggetto derivato;
  • riga 5: il dizionario che verrà costruito;
  • riga 7: si percorrono i valori di [self.__dict__] nella forma (chiave, valore);
  • riga 9: se la chiave corrente è presente nell'elenco delle chiavi da includere, viene aggiunta al dizionario [new_attributes] tramite la funzione [set_value] che descriveremo tra poco;
  • riga 12: se il parametro [included_keys] non è presente, viene utilizzato il parametro [excluded_keys]. Se la proprietà non fa parte delle proprietà da escludere, viene aggiunta al dizionario [new_attributes];
  • riga 12: esistono diversi modi per escludere una proprietà dal dizionario:
    • è stata definita a livello dell’attributo di classe [excluded_keys];
    • è stata definita nell’elenco [excluded_keys] passato alla funzione [asdict];
    • il parametro [included_keys] è presente e non include la proprietà;
  • riga 15: si restituisce il dizionario [new_attributes]

La funzione [set_value] delle righe 10 e 13 è la seguente:


    @staticmethod
    def set_value(key: str, value, new_attributes: dict):
        # le chiavi possono avere la forma __Class__key
        match = re.match("^.*?__(.*?)$", key)
        if match:
            # si annota la nuova chiave
            newkey = match.groups()[0]
        else:
            # la chiave rimane invariata
            newkey = key
        # si inserisce la nuova chiave nel dizionario [new_attributes]
        # trasformando, se necessario, il valore associato in uno dei tipi
        # dizionario, lista, tipo semplice
        new_attributes[newkey] = BaseEntity.check_value(value)

Commenti

  • riga 4: si verifica se la chiave è della forma __Class_key. Questa è la forma che assume se appartiene a un oggetto incluso nell’oggetto principale. In questo caso, si desidera conservare solo la stringa [key];
  • riga 7: si conserva solo la stringa che segue gli ultimi due caratteri sottolineati della stringa;
  • righe 8-10: se la chiave non è nella forma __Class_key, allora la si mantiene così com’è;
  • righe 11-14: il valore associato alla chiave [newkey] viene calcolato dal metodo statico [BaseEntity.check_value];

Il metodo statico [BaseEntity.check_value] è il seguente:


    @staticmethod
    def check_value(value):
        # il valore può essere di tipo BaseEntity, list, dict o un tipo semplice
        # il valore è un'istanza di BaseEntity?
        if isinstance(value, BaseEntity):
            value2 = value.asdict()
        # Il valore è di tipo lista?
        elif isinstance(value, list):
            value2 = BaseEntity.list2list(value)
        # Il valore è di tipo dict?
        elif isinstance(value, dict):
            value2 = BaseEntity.dict2dict(value)
        #è di tipo semplice
        else:
            value2 = value
        # si restituisce il risultato
        return value2
  • riga 1: il metodo [check_value] è statico (metodo di classe e non di istanza). Riceve come parametro il valore da associare a una chiave del dizionario:
    • riga 17: se tale valore è di tipo semplice, rimane invariato;
    • righe 5-6: se il valore è di tipo BaseEntity, viene sostituito con il proprio dizionario. Si ha quindi una chiamata ricorsiva;
    • righe 8-9: se questo valore è una lista, viene sostituito dal valore [BaseEntity.list2list];
    • righe 11-12: se il valore è un dizionario, viene sostituito dal valore [BaseEntity.dict2dict];

Il metodo statico [BaseEntity.list2list] è il seguente:


    @staticmethod
    def list2list(liste: list) -> list:
        # si esaminano gli elementi della lista
        newlist = []
        for value in liste:
            newlist.append(BaseEntity.check_value(value))
        # si restituisce la nuova lista
        return newlist
  • riga 2: il metodo riceve una lista e restituisce una lista;
  • righe 5-6: ogni valore della lista ricevuta come parametro viene sostituito dal valore restituito dal metodo statico [BaseEntity.check_value]. Si tratta quindi di una chiamata ricorsiva. Il metodo statico [BaseEntity.check_value] viene chiamato finché il suo parametro [value] non è un tipo semplice (non un tipo BaseEntity, né una lista né un dizionario);

Il metodo statico [BaseEntity.dict2dict] è il seguente:


    @staticmethod
    def dict2dict(dictionary: dict) -> dict:
        # si esaminano gli elementi del dizionario
        newdict = {}
        for key, value in dictionary.items():
            newdict[key] = BaseEntity.check_value(value)
        # si restituisce il nuovo dizionario
        return newdict
  • riga 2: il metodo riceve un dizionario e restituisce un dizionario;
  • righe 5-6: si sostituisce ogni valore del dizionario ricevuto come parametro con il valore restituito dal metodo statico [BaseEntity.check_value]. Si tratta quindi di una chiamata ricorsiva. Il metodo statico [BaseEntity.check_value] viene chiamato finché il suo parametro [value] non è un tipo semplice (non un tipo BaseEntity, né una lista né un dizionario);

13.2.2.2. Exemples

Lo script [asdict_01] mostra diversi utilizzi del metodo [asdict]:


# si configura l'applicazione
import config
config = config.configure()

# il syspath è configurato - è possibile eseguire le importazioni
from Enseignant import Enseignant
from BaseEntity import BaseEntity

# un docente
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)
# un altro docente
enseignant2 = Enseignant().fromdict({"id"2"nom""abélard""prénom""béatrice""âge"57})
print(enseignant2.asdict())
print(enseignant2.asdict(included_keys=["_Personne__nom"]))
# un elenco di entità all'interno di un'entità
Enseignant.excluded_keys = []
entity1 = BaseEntity()
enseignants = [enseignant1, enseignant2]
setattr(entity1, "enseignants", enseignants)
print(entity1.asdict())
# un dizionario di entità all'interno di un'entità
matières = {"maths": enseignant1, "français": enseignant2}
setattr(entity1, "matières", matières)
print(entity1.asdict())

I risultati dell’esecuzione sono i seguenti:


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
  • la riga 4 mostra il vantaggio del metodo [asdict] rispetto all’utilizzo della proprietà [__dict__]. Le proprietà vengono private del prefisso della loro classe. Ciò si presta meglio alla visualizzazione;
  • esistono diversi modi per utilizzare il metodo [asdict]:
    • se si desiderano tutte le proprietà: si utilizza il metodo [asdict] senza parametri;
    • se si desiderano solo alcune proprietà:
      • ci sono più proprietà da includere che da escludere: si utilizzerà il solo parametro [excluded_keys];
      • se le proprietà da includere sono meno di quelle da escludere: si utilizzerà il solo parametro [included_keys];

13.2.3. Il metodo [BaseEntity.asjson]

Questo metodo consente di ottenere la stringa jSON da un oggetto [BaseEntity] o derivato. Visualizza la stringa jSON del dizionario restituito dal metodo [asdict]. Il codice è il seguente:


def asjson(self, included_keys: list = None, excluded_keys: list = []) -> str:
        # la stringa JSON
        return json.dumps(self.asdict(included_keys=included_keys, excluded_keys=excluded_keys), ensure_ascii=False)
  • riga 1: i parametri del metodo [asjson] sono quelli del metodo [asdict];

Ecco un esempio (asjson_01) che utilizza questo metodo:


# si configura l'applicazione
import config
config = config.configure()

# il syspath è configurato - è possibile eseguire le importazioni
from Enseignant import Enseignant
from BaseEntity import BaseEntity

# un docente
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())
# un altro docente
enseignant2 = Enseignant().fromdict({"id"2"nom""abélard""prénom""béatrice""âge"57})
print(enseignant2.asjson())
print(enseignant2.asjson(included_keys=["_Personne__nom"]))
# un elenco di entità all'interno di un'entità
Enseignant.excluded_keys = []
entity1 = BaseEntity()
enseignants = [enseignant1, enseignant2]
setattr(entity1, "enseignants", enseignants)
print(entity1.asjson())
# un dizionario di entità all'interno di un'entità
matières = {"maths": enseignant1, "français": enseignant2}
setattr(entity1, "matières", matières)
print(entity1.asjson())

I risultati sono i seguenti:


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

Il metodo [BaseEntity.__str__] utilizza il metodo [asjson] per visualizzare l'identità dell'oggetto [BaseEntity] o di un suo derivato:


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

13.2.4. Il metodo [BaseEntity.fromjson]

Il metodo [BaseEntity.fromjson] consente di inizializzare un oggetto di tipo [BaseEntity] o derivato da un dizionario jSON. Il suo codice è il seguente:


def fromjson(self, json_state: str, silent: bool = False):
        # si aggiorna lo stato dell'oggetto a partire dalla stringa jSON
        return self.fromdict(json.loads(json_state), silent=silent)
  • riga 1: il metodo accetta due parametri:
    • [json_state]: il dizionario jSON che verrà utilizzato per inizializzare l'oggetto [BaseEntity];
    • [silent]: per indicare se la presenza nel dizionario jSON di una chiave che non può essere accettata come proprietà dell’oggetto [BaseEntity] provochi un’eccezione (silent=False) o venga semplicemente ignorata (silent=True);
  • riga 3: si inizia costruendo il dizionario Python che rispecchia il dizionario jSON, quindi si utilizza il metodo [fromdict] per inizializzare l’oggetto [BaseEntity] a partire da questo dizionario Python;

Ecco un esempio (fromjson_01):


# si configura l'applicazione
import config

config = config.configure()

# il syspath è configurato - è possibile eseguire le importazioni
from Enseignant import Enseignant
import json

# un docente
json1 = json.dumps({"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56})
enseignant1 = Enseignant().fromjson(json1)
enseignant1.show()
  • riga 11: si crea la stringa jSON da un dizionario;
  • riga 12: si inizializza un oggetto [Enseignant] con questa stringa;
  • riga 13: viene visualizzato il docente;

I risultati sono i seguenti:


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. Lo script [main]

Lo script [main] riassume i diversi metodi incontrati:


# si configura l'applicazione
import config

config = config.configure()

# il syspath è configurato - è possibile eseguire le importazioni
from BaseEntity import BaseEntity
from MyException import MyException


# una classe
class ChildEntity(BaseEntity):
    # attributi esclusi dallo stato della classe
    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})")


# configurazione ChildEntity
ChildEntity.excluded_keys = []
# istanza ChildEntity
child = ChildEntity().fromdict({"att1": 1, "att2": 2})
# attenzione ai nomi delle proprietà
# sono questi i nomi utilizzati in [excluded_keys] e [included_keys]
print(child.__dict__)
# proprietà senza prefisso della classe
print(child)

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

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

# esclusione di determinate chiavi dal report delle istanze
ChildEntity.excluded_keys = ['att3']
print(child)

# si esclude esplicitamente una chiave dalla visualizzazione
# viene aggiunta a quelle escluse globalmente a livello di classe
print(child.asdict(excluded_keys=["_ChildEntity__att1"]))
print(child.asjson(excluded_keys=["att2"]))

# rilevanza della classe rispetto al dizionario
# può verificare la validità del proprio contenuto
try:
    child = ChildEntity().fromdict({"att1": 20})
except MyException as erreur:
    print(erreur)

# istanza ChildEntity
child1 = ChildEntity().fromdict({"att1": 1, "att2": 2, "att3": 3, "att4": 4})
# istanza ChildEntity contenente un’altra istanza ChildEntity
child2 = ChildEntity().fromdict({"att1": 10, "att2": 20, "att3": 30, "att4": child1})
print(child2)

# included_keys ha la priorità su excluded_keys, che vengono quindi ignorate
ChildEntity.excluded_keys = ['_ChildEntity__att1', 'att2']
print(child.asdict(included_keys=["_ChildEntity__att1", "att3"], excluded_keys=["att3", "att4"]))

I risultati dell'esecuzione sono i seguenti:


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

Si presti attenzione alla riga 2 dei risultati: è la proprietà [ChildEntity.__dict__] (riga 38 del codice) che ci permette di conoscere i nomi delle proprietà da inserire negli elenchi [included_keys] e [excluded_keys]. Si noti, sempre nella riga 2 dei risultati, che a seconda che la proprietà sia definita all’interno della classe tramite un getter/setter o che sia stata creata come si creerebbe la chiave di un dizionario, essa è o meno preceduta dal nome della classe [ChildEntity].