13. Las clases genéricas [BaseEntity] y [MyException]
Ahora definimos dos clases que utilizaremos con frecuencia en lo sucesivo.

13.1. La clase MyException
La clase [MyException] (MyException.py) proporciona una clase de excepciones propia:
# una clase de excepción propia derivada de [BaseException]
class MyException(BaseException):
# constructor
def __init__(self: object, code: int, message: str):
# clase padre
BaseException.__init__(self, message)
# código de error
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):
# el código de error debe ser un entero positivo
if isinstance(code, int) and code > 0:
self.__code = code
else:
# excepción
raise BaseException(f"code erreur {code} incorrect")
Notas
- línea 2: la clase [MyException] deriva de la clase predefinida [BaseException];
- línea 4: el constructor acepta dos parámetros:
- [code]: un código de error entero;
- [message]: un mensaje de error;
- línea 6: se pasa el mensaje de error a la clase padre;
- líneas 14-27: el atributo [code] se maneja mediante un getter/setter;
- líneas 23-24: se verifica la validez del atributo [code]: debe ser un entero mayor que 0;
13.2. La clase [BaseEntity]
La clase [BaseEntity] será la clase padre de la mayoría de las clases que crearemos para encapsular información sobre un objeto. En lo sucesivo, utilizaremos principalmente dos tipos de clases:
- clases cuyo único propósito es encapsular en un mismo lugar la información sobre un mismo objeto. Estas no tendrán comportamientos (métodos) más allá de los getter/setter y una función de visualización (__str__). Si hay N objetos que gestionar, estas clases se instancian N veces. [BaseEntity] será la clase padre de este tipo de clases;
- clases cuya función principal es encapsular métodos y muy poca información. Estas clases solo se instanciarán una vez (singleton). Su función es implementar los algoritmos de una aplicación;
La clase [BaseEntity] es la siguiente:
# importaciones
import json
import re
from MyException import MyException
class BaseEntity(object):
# propiedades excluidas del estado de la clase
excluded_keys = []
# propiedades de la clase
@staticmethod
def get_allowed_keys() -> list:
# id: identificador del objeto
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):
# el id debe ser un entero >=0
try:
id = int(id)
erreur = id < 0
except:
erreur = True
# ¿error?
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):
…
Comentarios
- El objetivo de la clase [BaseEntity] es facilitar las conversiones Objeto / Diccionario y Objeto / jSON. De este modo, se ofrecen los siguientes métodos:
- [asdict]: devuelve el diccionario de propiedades del objeto;
- [fromdict]: crea un objeto a partir de un diccionario;
- [asjson]: devuelve la cadena jSON del objeto, tal como lo hace la función [__str__];
- [fromjson]: crea un objeto a partir de su cadena jSON;
- la clase [BaseEntity] está diseñada para ser derivada y no para ser utilizada tal cual;
- líneas 22-25: la clase [BaseEntity] solo tiene una propiedad, el entero [id]. Esta propiedad es el identificador del objeto. En la práctica, a menudo resulta útil poder diferenciar las instancias de una misma clase. Lo haremos con esta propiedad, que es única para cada instancia. Además, los objetos suelen provenir de bases de datos donde se identifican mediante una clave primaria, generalmente un entero. En estos casos, [id] será la clave primaria;
- líneas 27-40: el setter de la propiedad [id]. Se verifica que sea un entero >=0. Si no es así, se lanza una excepción de tipo [MyException] (línea 39);
- línea 10: [excluded_keys] es un atributo de clase y no de instancia. Por lo tanto, se escribirá [BaseEntity.excluded_keys]. Este atributo de clase es una lista que contiene las propiedades de clase que no participan en las conversiones Objeto / Diccionario y Objeto / jSON;
- líneas 12-16: [get_allowed_keys] devuelve la lista de propiedades de la clase. En una conversión de Diccionario a Objeto o de jSON a Objeto, solo se aceptarán las claves presentes en esta lista. Cada clase que derive de la clase [BaseEntity] deberá redefinir esta lista;
Es importante entender aquí que las propiedades y funciones de la clase [BaseEntity] son accesibles para las clases derivadas de [BaseEntity]. Este es el punto clave que hay que comprender.
Vamos a detallar el código de la clase [BaseEntity]. Es bastante avanzado. El lector principiante puede limitarse a leer la función de cada método sin detenerse en su código.
13.2.1. El método [BaseEntity.fromdict]
13.2.1.1. Définition
El método [fromdict] permite inicializar un objeto [BaseEntity] o derivado a partir de un diccionario:
def fromdict(self, state: dict, silent=False):
# se está actualizando el objeto
# claves autorizadas
allowed_keys = self.__class__.get_allowed_keys()
# recorre las claves de estado
for key, value in state.items():
# ¿Está autorizada la clave?
if key not in allowed_keys:
if not silent:
raise MyException(2, f"la clé {key} n'est pas autorisée")
else:
# se intenta asignar el valor a la clave
# se permite que se propague cualquier excepción
setattr(self, key, value)
# se devuelve el objeto
return self
Comentarios
- línea 1: la función recibe como parámetro el diccionario [state] a partir del cual se inicializará el objeto actual;
- línea 4: se invoca la función estática [get_allowed_keys] de la clase que llamó a la función [fromdict]. Si se trata de una clase derivada de [BaseEntity] y dicha clase derivada ha redefinido la función estática [get_allowed_keys], entonces se llama a la función [get_allowed_keys]. Cada clase derivada redefine esta función estática para declarar sus propiedades;
- línea 6: se recorren las claves y los valores del diccionario [state];
- línea 8: si la clave [key] no forma parte de las propiedades de la clase, entonces:
- se ignora;
- se lanza una excepción (línea 10). El desarrollador indica lo que desea pasando el parámetro correcto [silent] (línea 1). El valor por defecto de [silent] hace que se lance una excepción si se intenta inicializar el objeto con una propiedad que no tiene;
- línea 14: si la clave forma parte de las propiedades del objeto, entonces se le asigna al objeto [self] mediante la función predefinida [setattr];
- línea 16: la función devuelve el objeto inicializado;
13.2.1.2. Exemples

13.2.1.2.1. La clase [Utils]
La clase [Utils] (Utils.py) es la siguiente:
class Utils:
# método estático
@staticmethod
def is_string_ok(string: str) -> bool:
# ¿string es una cadena?
erreur = not isinstance(string, str)
if not erreur:
# ¿La cadena está vacía?
erreur = string.strip() == ''
# resultado
return not erreur
En las líneas 3 a 11, define un método estático que devuelve un valor booleano verdadero si su parámetro [str] es una cadena de caracteres no vacía;
13.2.1.2.2. La clase [Personne]
La clase [Personne] (Personne.py) deriva de la clase [BaseEntity]:
# importaciones
from BaseEntity import BaseEntity
from MyException import MyException
from Utils import Utils
# clase Persona
class Personne(BaseEntity):
# propiedades excluidas del estado de la clase
excluded_keys = []
# propiedades de la clase
# id: identificador de la persona
# nombre: nombre de la persona
# apellido: apellido de la persona
# edad: edad de la persona
@staticmethod
def get_allowed_keys() -> list:
# id: identificador del objeto
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):
# el nombre debe ser distinto de vacío
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):
# el nombre debe ser distinto de vacío
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):
# la edad debe ser un número entero >=0
erreur = False
if isinstance(âge, int):
if âge >= 0:
self.__âge = âge
else:
erreur = True
else:
erreur = True
# ¿Error?
if erreur:
raise MyException(13, "L'âge doit être un entier >=0")
- línea 8: la clase [Personne] deriva de la clase [BaseEntity];
- líneas 8-65: se ha conservado lo esencial de la clase [Personne] que ya se ha visto. Las diferencias son las siguientes:
- la clase ya no tiene un constructor;
- la clase utiliza la excepción [MyException], como se ve en la línea 65;
- cuenta con un método estático, [get_allowed_keys], líneas 17-20, que define la lista de sus propiedades. Las propiedades propias de la clase [Personne] se suman a las de la clase padre [BaseEntity];
- cuenta con una lista estática [excluded_keys], sobre la que volveremos más adelante;
13.2.1.2.3. La clase [Enseignant]
La clase [Enseignant] (Enseignant.py) deriva de la clase [Personne]:
# importaciones
from MyException import MyException
from Personne import Personne
from Utils import Utils
# clase Profesor
class Enseignant(Personne):
# propiedades excluidas del estado de la clase
excluded_keys = []
# propiedades de la clase
# id: identificador de la persona
# nombre: nombre de la persona
# apellido: apellido de la persona
# edad: edad de la persona
# materia: materia que se imparte
@staticmethod
def get_allowed_keys() -> list:
# id: identificador del objeto
return Personne.get_allowed_keys() + ["discipline"]
# propiedades
@property
def discipline(self) -> str:
return self.__discipline
@discipline.setter
def discipline(self, discipline: str):
# la disciplina debe ser una cadena no vacía
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")
# método show
def show(self):
print(f"Enseignant[{self.id}, {self.prénom}, {self.nom}, {self.âge}]")
- línea 8: la clase [Enseignant] extiende (o deriva) de la clase [Personne];
- líneas 18-21: definen la lista de propiedades de la clase;
- líneas 37-38: el método [show] muestra la identidad del profesor;
13.2.1.2.4. La configuración [config]
Los scripts de ejemplo utilizan la siguiente configuración [config]:
def configure():
import os
# carpeta del archivo de configuración
script_dir = os.path.dirname(os.path.abspath(__file__))
# rutas absolutas de las carpetas que se deben incluir en el syspath
absolute_dependencies = [
# la clase BaseEntity
f"{script_dir}/entities",
]
# actualización del syspath
from myutils import set_syspath
set_syspath(absolute_dependencies)
# se realiza la configuración
return {}
- líneas 8-10: las carpetas que contienen las dependencias del proyecto;
- líneas 14-15: se construye la ruta de Python;
- línea 18: se devuelve un diccionario vacío (no hay otra configuración más que la de syspath);
13.2.1.2.5. El script [fromdict_01]
El script [fromdict_01] es el siguiente:
# se configura la aplicación
import config
config = config.configure()
# El syspath está configurado; ya se pueden realizar las importaciones
from Enseignant import Enseignant
# un profesor
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56})
enseignant1.show()
- línea 10: se crea un objeto [Enseignant] a partir de un diccionario. Para ello, se utiliza el constructor predeterminado de la clase para crear un objeto [Enseignant] al que se le aplica el método [fromdict]. Hay que tener en cuenta que, en este caso, el método [fromdict] que se ejecuta es el de la clase padre [BaseEntity]. De hecho:
- primero se busca el método [fromdict] en la clase [Enseignant]. No existe;
- luego se busca en la clase padre [Personne]. No existe;
- luego se busca en la clase padre [BaseEntity]. Existe;
- línea 11: se muestra el objeto [Enseignant];
Los resultados son los siguientes:
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. El script [fromdict_02]
El script [fromdict_02] es el siguiente:
# se configura la aplicación
import config
config = config.configure()
# El syspath está configurado; ya se pueden realizar las importaciones
from Enseignant import Enseignant
# un profesor
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "", "âge": 56})
enseignant1.show()
- línea 10: se crea un maestro con un nombre en blanco. Esto debe generar una excepción, ya que la clase [Personne] no acepta nombres en blanco. Este ejemplo muestra la diferencia entre un diccionario y un objeto. Este último puede verificar la validez de sus propiedades, pero el diccionario no;
Los resultados son los siguientes:
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. El script [fromdict_03]
El script [fromdict_03] es el siguiente:
# se configura la aplicación
import config
config = config.configure()
# El syspath está configurado; ya se pueden realizar las importaciones
from Enseignant import Enseignant
# un profesor
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "albert", "âge": 56, "sexe": "M"})
enseignant1.show()
- línea 10: se crea un maestro a partir de un diccionario que contiene una clave (género) que no pertenece a la clase [Enseignant]. En ese caso, debería generarse una excepción;
Los resultados son los siguientes:
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. El script [fromdict_04]
El script [fromdict_04] es una copia de [fromdict_03] con una sola diferencia:
# se configura la aplicación
import config
config = config.configure()
# El syspath está configurado; ya se pueden realizar las importaciones
from Enseignant import Enseignant
# un profesor
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "albert", "âge": 56, "sexe": "M"}, silent=True)
enseignant1.show()
- línea 10: se utilizó el parámetro [silent=True] para indicar que, si una clave del diccionario no es una propiedad de la clase [Enseignant], simplemente debe ignorarse. En este caso, no se lanzará ninguna excepción;
Los resultados son los siguientes:
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. El método [BaseEntity.asdict]
13.2.2.1. Définition
El método [BaseEntity.asdict] devuelve un diccionario cuyas claves son las propiedades del objeto:
def asdict(self, included_keys: list = None, excluded_keys: list =[]) -> dict:
# atributos del objeto
attributes = self.__dict__
# los nuevos atributos
new_attributes = {}
# se recorren los atributos
for key, value in attributes.items():
# si se solicita explícitamente la clave
if included_keys and key in included_keys:
self.set_value(key, value, new_attributes)
# de lo contrario, si la clave no está excluida
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)
# se devuelve el diccionario de atributos
return new_attributes
Comentarios
- línea 1: la función [asdict] devuelve el diccionario de propiedades del objeto;
- línea 1: [included_keys]: la lista de claves que se deben incluir en el diccionario;
- línea 1: [excluded_keys]: la lista de claves que se deben excluir del diccionario;
- línea 3: la propiedad [self.__dict__] devuelve el diccionario de propiedades del objeto. Los nombres de las propiedades son las claves y sus valores, los valores del diccionario. Un objeto puede contener referencias a otros objetos. En ese caso, los nombres de las propiedades llevan como prefijo el nombre de la clase a la que pertenecen. Esto es algo que no queremos. Queremos las propiedades sin su prefijo;
- línea 3: hay que entender aquí que si la función [asdict] se ejecuta dentro de una clase derivada de [BaseEntity], la propiedad [self.__dict__] devuelve el diccionario de propiedades del objeto derivado;
- línea 5: el diccionario que se va a construir;
- línea 7: se recorren los valores de [self.__dict__] en el formato (clave, valor);
- línea 9: si la clave actual figura en la lista de claves que se deben incluir, entonces se agrega al diccionario [new_attributes] mediante la función [set_value], que describiremos a continuación;
- línea 12: si el parámetro [included_keys] no está presente, entonces se utiliza el parámetro [excluded_keys]. Si la propiedad no forma parte de las propiedades que se deben excluir, entonces se agrega al diccionario [new_attributes];
- línea 12: hay varias formas de excluir una propiedad del diccionario:
- se ha definido a nivel del atributo de clase [excluded_keys];
- se ha definido en la lista [excluded_keys] pasada a la función [asdict];
- el parámetro [included_keys] está presente y no incluye la propiedad;
- línea 15: se devuelve el diccionario [new_attributes]
La función [set_value] de las líneas 10 y 13 es la siguiente:
@staticmethod
def set_value(key: str, value, new_attributes: dict):
# las claves pueden tener el formato __Class__key
match = re.match("^.*?__(.*?)$", key)
if match:
# se anota la nueva clave
newkey = match.groups()[0]
else:
# la clave permanece sin cambios
newkey = key
# se inserta la nueva clave en el diccionario [new_attributes]
# transformando, si es necesario, el valor asociado a uno de los tipos
# diccionario, lista, tipo simple
new_attributes[newkey] = BaseEntity.check_value(value)
Comentarios
- línea 4: se verifica si la clave tiene el formato __Class_key. Este es el formato que tiene si pertenece a un objeto incluido en el objeto principal. En este caso, solo se desea conservar la cadena [key];
- línea 7: solo se conserva la cadena que sigue a los dos últimos caracteres subrayados de la cadena;
- líneas 8-10: si la clave no tiene el formato __Class_key, entonces se conserva tal cual;
- líneas 11-14: el valor asociado a la clave [newkey] se calcula mediante el método estático [BaseEntity.check_value];
El método estático [BaseEntity.check_value] es el siguiente:
@staticmethod
def check_value(value):
# el valor puede ser de tipo BaseEntity, lista, diccionario o un tipo simple
# ¿El valor es una instancia de BaseEntity?
if isinstance(value, BaseEntity):
value2 = value.asdict()
#es de tipo lista?
elif isinstance(value, list):
value2 = BaseEntity.list2list(value)
#es de tipo diccionario?
elif isinstance(value, dict):
value2 = BaseEntity.dict2dict(value)
#es de tipo simple?
else:
value2 = value
# se devuelve el resultado
return value2
- línea 1: el método [check_value] es estático (método de clase y no de instancia). Recibe como parámetro el valor que se asociará a una clave del diccionario:
- línea 17: si este valor es de tipo simple, permanece sin cambios;
- líneas 5-6: si este valor es de tipo BaseEntity, el valor se reemplaza por su diccionario. Entonces se produce una llamada recursiva;
- líneas 8-9: si este valor es una lista, entonces se reemplaza por el valor [BaseEntity.list2list];
- líneas 11-12: si este valor es un diccionario, se reemplaza por el valor [BaseEntity.dict2dict];
El método estático [BaseEntity.list2list] es el siguiente:
@staticmethod
def list2list(liste: list) -> list:
# se revisan los elementos de la lista
newlist = []
for value in liste:
newlist.append(BaseEntity.check_value(value))
# se devuelve la nueva lista
return newlist
- línea 2: el método recibe una lista y devuelve una lista;
- líneas 5-6: se reemplaza cada valor de la lista recibida como parámetro por el valor devuelto por el método estático [BaseEntity.check_value]. Por lo tanto, se trata de una llamada recursiva. El método estático [BaseEntity.check_value] se invoca hasta que su parámetro [value] sea un tipo simple (no un tipo BaseEntity, ni una lista ni un diccionario);
El método estático [BaseEntity.dict2dict] es el siguiente:
@staticmethod
def dict2dict(dictionary: dict) -> dict:
# se revisan los elementos del diccionario
newdict = {}
for key, value in dictionary.items():
newdict[key] = BaseEntity.check_value(value)
# se devuelve el nuevo diccionario
return newdict
- línea 2: el método recibe un diccionario y devuelve un diccionario;
- líneas 5-6: se reemplaza cada valor del diccionario recibido como parámetro por el valor devuelto por el método estático [BaseEntity.check_value]. Por lo tanto, se trata de una llamada recursiva. El método estático [BaseEntity.check_value] se invoca hasta que su parámetro [value] sea un tipo simple (no un tipo BaseEntity, ni una lista ni un diccionario);
13.2.2.2. Exemples
El script [asdict_01] muestra diversos usos del método [asdict]:
# se configura la aplicación
import config
config = config.configure()
# El syspath está configurado; ya se pueden realizar las importaciones
from Enseignant import Enseignant
from BaseEntity import BaseEntity
# un profesor
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)
# otro profesor
enseignant2 = Enseignant().fromdict({"id": 2, "nom": "abélard", "prénom": "béatrice", "âge": 57})
print(enseignant2.asdict())
print(enseignant2.asdict(included_keys=["_Personne__nom"]))
# una lista de entidades dentro de una entidad
Enseignant.excluded_keys = []
entity1 = BaseEntity()
enseignants = [enseignant1, enseignant2]
setattr(entity1, "enseignants", enseignants)
print(entity1.asdict())
# un diccionario de entidades dentro de una entidad
matières = {"maths": enseignant1, "français": enseignant2}
setattr(entity1, "matières", matières)
print(entity1.asdict())
Los resultados de la ejecución son los siguientes:
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 línea 4 muestra la ventaja del método [asdict] frente al uso de la propiedad [__dict__]. A las propiedades se les elimina el prefijo de su clase. Esto facilita su visualización;
- hay varias formas de utilizar el método [asdict]:
- si queremos todas las propiedades: utilizamos el método [asdict] sin parámetros;
- si solo queremos ciertas propiedades:
- Hay más propiedades que incluir que excluir: se utilizará únicamente el parámetro [excluded_keys];
- hay menos propiedades que incluir que excluir: se utilizará únicamente el parámetro [included_keys];
13.2.3. El método [BaseEntity.asjson]
Este método permite obtener la cadena jSON de un objeto [BaseEntity] o derivado. Muestra la cadena jSON del diccionario devuelto por el método [asdict]. Su código es el siguiente:
def asjson(self, included_keys: list = None, excluded_keys: list = []) -> str:
# la cadena JSON
return json.dumps(self.asdict(included_keys=included_keys, excluded_keys=excluded_keys), ensure_ascii=False)
- línea 1: los parámetros del método [asjson] son los mismos que los del método [asdict];
A continuación se muestra un ejemplo (asjson_01) que utiliza este método:
# se configura la aplicación
import config
config = config.configure()
# el syspath está configurado; ya se pueden realizar las importaciones
from Enseignant import Enseignant
from BaseEntity import BaseEntity
# un profesor
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())
# otro profesor
enseignant2 = Enseignant().fromdict({"id": 2, "nom": "abélard", "prénom": "béatrice", "âge": 57})
print(enseignant2.asjson())
print(enseignant2.asjson(included_keys=["_Personne__nom"]))
# una lista de entidades dentro de una entidad
Enseignant.excluded_keys = []
entity1 = BaseEntity()
enseignants = [enseignant1, enseignant2]
setattr(entity1, "enseignants", enseignants)
print(entity1.asjson())
# un diccionario de entidades dentro de una entidad
matières = {"maths": enseignant1, "français": enseignant2}
setattr(entity1, "matières", matières)
print(entity1.asjson())
Los resultados son los siguientes:
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
El método [BaseEntity.__str__] utiliza el método [asjson] para mostrar la identidad del objeto [BaseEntity] o de un derivado:
# toString
def __str__(self) -> str:
return self.asjson()
13.2.4. El método [BaseEntity.fromjson]
El método [BaseEntity.fromjson] permite inicializar un objeto de tipo [BaseEntity] o derivado a partir de un diccionario jSON. Su código es el siguiente:
def fromjson(self, json_state: str, silent: bool = False):
# se actualiza el estado del objeto a partir de la cadena jSON
return self.fromdict(json.loads(json_state), silent=silent)
- línea 1: el método admite dos parámetros:
- [json_state]: el diccionario jSON que se utilizará para inicializar el objeto [BaseEntity];
- [silent]: para indicar si la presencia en el diccionario jSON de una clave que no puede aceptarse como propiedad del objeto [BaseEntity] provoca una excepción (silent=False) o simplemente se ignora (silent=True);
- línea 3: primero se construye el diccionario de Python que representa el diccionario jSON, y luego se utiliza el método [fromdict] para inicializar el objeto [BaseEntity] a partir de ese diccionario de Python;
A continuación se muestra un ejemplo (fromjson_01):
# se configura la aplicación
import config
config = config.configure()
# se configura el syspath; ya se pueden realizar las importaciones
from Enseignant import Enseignant
import json
# un profesor
json1 = json.dumps({"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56})
enseignant1 = Enseignant().fromjson(json1)
enseignant1.show()
- línea 11: se crea la cadena jSON a partir de un diccionario;
- línea 12: se inicializa un objeto [Enseignant] con esta cadena;
- línea 13: se muestra el profesor;
Los resultados son los siguientes:
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. El script [main]
El script [main] resume los distintos métodos encontrados:
# se configura la aplicación
import config
config = config.configure()
# El syspath está configurado; ya se pueden realizar las importaciones
from BaseEntity import BaseEntity
from MyException import MyException
# una clase
class ChildEntity(BaseEntity):
# atributos excluidos del estado de la clase
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})")
# configuración ChildEntity
ChildEntity.excluded_keys = []
# instancia ChildEntity
child = ChildEntity().fromdict({"att1": 1, "att2": 2})
# cuidado con los nombres de las propiedades
# Estos son los nombres que se utilizan en [excluded_keys] y [included_keys]
print(child.__dict__)
# propiedades sin el prefijo de su clase
print(child)
# instancia ChildEntity
try:
child = ChildEntity().fromdict({"att1": 1, "att5": 5})
print(child)
except MyException as erreur:
print(erreur)
# instancia ChildEntity
child = ChildEntity().fromdict({"att1": 1, "att2": 2, "att3": 3, "att4": 4})
print(child)
# exclusiones de ciertas claves del informe de instancias
ChildEntity.excluded_keys = ['att3']
print(child)
# se excluye explícitamente una clave de la visualización
# se agrega a las excluidas globalmente a nivel de la clase
print(child.asdict(excluded_keys=["_ChildEntity__att1"]))
print(child.asjson(excluded_keys=["att2"]))
# Relación de la clase con el diccionario
# puede verificar la validez de su contenido
try:
child = ChildEntity().fromdict({"att1": 20})
except MyException as erreur:
print(erreur)
# instancia ChildEntity
child1 = ChildEntity().fromdict({"att1": 1, "att2": 2, "att3": 3, "att4": 4})
# instancia ChildEntity que contiene otra instancia ChildEntity
child2 = ChildEntity().fromdict({"att1": 10, "att2": 20, "att3": 30, "att4": child1})
print(child2)
# included_keys tiene prioridad sobre excluded_keys, las cuales, por lo tanto, se ignoran
ChildEntity.excluded_keys = ['_ChildEntity__att1', 'att2']
print(child.asdict(included_keys=["_ChildEntity__att1", "att3"], excluded_keys=["att3", "att4"]))
Los resultados de la ejecución son los siguientes:
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
Prestemos atención a la línea 2 de los resultados: es la propiedad [ChildEntity.__dict__] (línea 38 del código) la que nos permite conocer los nombres de las propiedades que deben incluirse en las listas [included_keys] y [excluded_keys]. Cabe señalar, siempre en la línea 2 de los resultados, que dependiendo de si la propiedad está definida dentro de la clase mediante un getter/setter o si se ha creado como se crearía la clave de un diccionario, lleva o no el prefijo del nombre de la clase [ChildEntity].