13. As classes genéricas [BaseEntity] e [MyException]
Definiremos agora duas classes que utilizaremos regularmente daqui em diante.

13.1. A classe MyException
A classe [MyException] (MyException.py) fornece uma classe de exceções proprietária:
# uma classe de exceção proprietária derivada de [BaseException]
class MyException(BaseException):
# construtor
def __init__(self: object, code: int, message: str):
# pai
BaseException.__init__(self, message)
# código de erro
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):
# o código de erro deve ser um número inteiro positivo
if isinstance(code, int) and code > 0:
self.__code = code
else:
# exceção
raise BaseException(f"code erreur {code} incorrect")
Notas
- linha 2: a classe [MyException] deriva da classe predefinida [BaseException];
- linha 4: o construtor aceita dois parâmetros:
- [code]: um código de erro inteiro;
- [message]: uma mensagem de erro;
- linha 6: a mensagem de erro é passada para a classe pai;
- linhas 14-27: o atributo [code] é manipulado por meio de um getter/setter;
- linhas 23-24: a validade do atributo [code] é verificada: ele deve ser um inteiro > 0;
13.2. A classe [BaseEntity]
A classe [BaseEntity] será a classe pai da maioria das classes que criaremos para encapsular informações sobre um objeto. Daqui em diante, utilizaremos principalmente dois tipos de classes:
- classes cujo único objetivo é encapsular, em um único local, informações sobre um mesmo objeto. Essas classes não terão comportamentos (métodos) além de getters/setters e uma função de exibição (__str__). Se houver N objetos a serem gerenciados, essas classes serão instanciadas N vezes. [BaseEntity] será a classe pai desse tipo de classes;
- classes cuja função principal é encapsular métodos e muito poucas informações. Essas classes serão instanciadas apenas uma vez (singleton). Sua função é implementar os algoritmos de uma aplicação;
A classe [BaseEntity] é a seguinte:
# importações
import json
import re
from MyException import MyException
class BaseEntity(object):
# propriedades excluídas do estado da classe
excluded_keys = []
# propriedades da classe
@staticmethod
def get_allowed_keys() -> list:
# id: identificador do 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):
# o id deve ser um número inteiro >=0
try:
id = int(id)
erreur = id < 0
except:
erreur = True
# erro?
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):
…
Comentários
- o objetivo da classe [BaseEntity] é facilitar as conversões Objeto / Dicionário e Objeto / jSON. Assim, são oferecidos os seguintes métodos:
- [asdict]: retorna o dicionário de propriedades do objeto;
- [fromdict]: cria um objeto a partir de um dicionário;
- [asjson]: retorna a string jSON do objeto, da mesma forma que a função [__str__];
- [fromjson]: cria um objeto a partir de sua string jSON;
- a classe [BaseEntity] destina-se a ser derivada e não a ser utilizada tal como está;
- linhas 22-25: a classe [BaseEntity] possui apenas uma propriedade, o inteiro [id]. Essa propriedade é o identificador do objeto. Na prática, muitas vezes é útil poder diferenciar as instâncias de uma mesma classe. Faremos isso com essa propriedade, que é única para cada instância. Além disso, os objetos geralmente provêm de bancos de dados, onde são identificados por uma chave primária, geralmente um inteiro. Nesses casos, [id] será a chave primária;
- linhas 27-40: o setter da propriedade [id]. Verifica-se se é um número inteiro >=0. Caso contrário, é lançada uma exceção do tipo [MyException] (linha 39);
- linha 10: [excluded_keys] é um atributo de classe e não de instância. Portanto, escreveremos [BaseEntity.excluded_keys]. Esse atributo de classe é uma lista que contém as propriedades da classe que não participam das conversões Objeto / Dicionário e Objeto / jSON;
- linhas 12-16: [get_allowed_keys] retorna a lista de propriedades da classe. Em uma conversão de Dicionário para Objeto ou de jSON para Objeto, serão aceitas apenas as chaves presentes nessa lista. Cada classe derivada da classe [BaseEntity] deverá redefinir essa lista;
É preciso entender aqui que as propriedades e funções da classe [BaseEntity] estão acessíveis às classes derivadas de [BaseEntity]. Esse é o ponto importante a ser compreendido.
Vamos detalhar o código da classe [BaseEntity]. Ele é bastante avançado. O leitor iniciante pode se limitar a ler a função de cada método sem se aprofundar no código.
13.2.1. O método [BaseEntity.fromdict]
13.2.1.1. Définition
O método [fromdict] permite inicializar um objeto [BaseEntity] ou derivado a partir de um dicionário:
def fromdict(self, state: dict, silent=False):
# atualizando o objeto
# chaves autorizadas
allowed_keys = self.__class__.get_allowed_keys()
# percorre as chaves de estado
for key, value in state.items():
# a chave está autorizada?
if key not in allowed_keys:
if not silent:
raise MyException(2, f"la clé {key} n'est pas autorisée")
else:
# tentando atribuir o valor à chave
# permitimos que uma eventual exceção seja propagada
setattr(self, key, value)
# retornamos o objeto
return self
Comentários
- linha 1: a função recebe como parâmetro o dicionário [state] a partir do qual o objeto atual será inicializado;
- linha 4: é chamada a função estática [get_allowed_keys] da classe que chamou a função [fromdict]. Se estivermos lidando com uma classe derivada de [BaseEntity] e essa classe derivada tiver redefinido a função estática [get_allowed_keys], então é a função [get_allowed_keys] que é chamada. Cada classe derivada redefine essa função estática para declarar suas propriedades;
- linha 6: percorremos as chaves e os valores do dicionário [state];
- linha 8: se a chave [key] não fizer parte das propriedades da classe, então:
- ela é ignorada;
- lança-se uma exceção (linha 10). O desenvolvedor indica o que deseja passando o parâmetro correto [silent] (linha 1). O valor padrão de [silent] faz com que uma exceção seja lançada se for tentada a inicialização do objeto com uma propriedade que ele não possui;
- linha 14: se a chave fizer parte das propriedades do objeto, ela é atribuída ao objeto [self] por meio da função predefinida [setattr];
- linha 16: a função retorna o objeto inicializado;
13.2.1.2. Exemples

13.2.1.2.1. A classe [Utils]
A classe [Utils] (Utils.py) é a seguinte:
class Utils:
# método estático
@staticmethod
def is_string_ok(string: str) -> bool:
# string é uma cadeia de caracteres
erreur = not isinstance(string, str)
if not erreur:
# a string está vazia?
erreur = string.strip() == ''
# resultado
return not erreur
Ela define, nas linhas 3 a 11, um método estático que retorna um valor booleano verdadeiro se seu parâmetro [str] for uma cadeia de caracteres não vazia;
13.2.1.2.2. A classe [Personne]
A classe [Personne] (Personne.py) deriva da classe [BaseEntity]:
# importações
from BaseEntity import BaseEntity
from MyException import MyException
from Utils import Utils
# classe Pessoa
class Personne(BaseEntity):
# propriedades excluídas do estado da classe
excluded_keys = []
# propriedades da classe
# id: identificador da pessoa
# nome: nome da pessoa
# sobrenome: sobrenome da pessoa
# idade: idade da pessoa
@staticmethod
def get_allowed_keys() -> list:
# id: identificador do 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):
# o nome deve ser diferente de vazio
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):
# o nome deve ser diferente de vazio
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):
# a idade deve ser um número inteiro >=0
erreur = False
if isinstance(âge, int):
if âge >= 0:
self.__âge = âge
else:
erreur = True
else:
erreur = True
# erro?
if erreur:
raise MyException(13, "L'âge doit être un entier >=0")
- linha 8: a classe [Personne] deriva da classe [BaseEntity];
- linhas 8 a 65: manteve-se a essência da classe [Personne] já encontrada. As diferenças são as seguintes:
- a classe não possui mais um construtor;
- a classe utiliza a exceção [MyException], exemplo na linha 65;
- ela possui um método estático, [get_allowed_keys], linhas 17-20, que define a lista de suas propriedades. As propriedades específicas da classe [Personne] são adicionadas às da classe pai [BaseEntity];
- ela possui uma lista estática [excluded_keys], à qual voltaremos mais tarde;
13.2.1.2.3. A classe [Enseignant]
A classe [Enseignant] (Enseignant.py) deriva da classe [Personne]:
# importações
from MyException import MyException
from Personne import Personne
from Utils import Utils
# classe Professor
class Enseignant(Personne):
# propriedades excluídas do estado da classe
excluded_keys = []
# propriedades da classe
# id: identificador da pessoa
# nome: nome da pessoa
# sobrenome: sobrenome da pessoa
# idade: idade da pessoa
# disciplina: disciplina ministrada
@staticmethod
def get_allowed_keys() -> list:
# id: identificador do objeto
return Personne.get_allowed_keys() + ["discipline"]
# propriedades
@property
def discipline(self) -> str:
return self.__discipline
@discipline.setter
def discipline(self, discipline: str):
# a disciplina deve ser uma sequência de caracteres não vazia
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}]")
- linha 8: a classe [Enseignant] estende (ou deriva) da classe [Personne];
- linhas 18-21: definem a lista de propriedades da classe;
- linhas 37-38: o método [show] exibe a identidade do professor;
13.2.1.2.4. A configuração [config]
Os scripts de exemplo utilizam a seguinte configuração [config]:
def configure():
import os
# pasta do arquivo de configuração
script_dir = os.path.dirname(os.path.abspath(__file__))
# caminhos absolutos das pastas a serem incluídas no syspath
absolute_dependencies = [
# a classe BaseEntity
f"{script_dir}/entities",
]
# atualização do syspath
from myutils import set_syspath
set_syspath(absolute_dependencies)
# fazemos a configuração
return {}
- linhas 8-10: as pastas que contêm as dependências do projeto;
- linhas 14-15: o Python Path é construído;
- linha 18: retorna-se um dicionário vazio (não há outras configurações além da do syspath);
13.2.1.2.5. O script [fromdict_01]
O script [fromdict_01] é o seguinte:
# configurando o aplicativo
import config
config = config.configure()
# o syspath está configurado — já é possível fazer as importações
from Enseignant import Enseignant
# um professor
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56})
enseignant1.show()
- linha 10: cria-se um objeto [Enseignant] a partir de um dicionário. Para isso, utiliza-se o construtor padrão da classe para criar um objeto [Enseignant], ao qual se aplica o método [fromdict]. É importante entender que, neste caso, o método [fromdict] executado é o da classe pai [BaseEntity]. De fato:
- o método [fromdict] é procurado primeiro na classe [Enseignant]. Ele não existe;
- em seguida, é procurado na classe pai [Personne]. Ele não existe;
- em seguida, é procurado na classe pai [BaseEntity]. Ele existe;
- linha 11: exibe-se o objeto [Enseignant];
Os resultados são os seguintes:
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. O script [fromdict_02]
O script [fromdict_02] é o seguinte:
# configurando o aplicativo
import config
config = config.configure()
# o syspath está configurado — já é possível fazer as importações
from Enseignant import Enseignant
# um professor
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "", "âge": 56})
enseignant1.show()
- linha 10: cria-se um professor com o nome em branco. Isso deve gerar uma exceção, pois a classe [Personne] não aceita nomes em branco. Este exemplo mostra a diferença entre um dicionário e um objeto. O objeto pode verificar a validade de suas propriedades, mas o dicionário não;
Os resultados são os seguintes:
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. O script [fromdict_03]
O script [fromdict_03] é o seguinte:
# configura-se o aplicativo
import config
config = config.configure()
# o syspath está configurado — já é possível fazer as importações
from Enseignant import Enseignant
# um professor
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "albert", "âge": 56, "sexe": "M"})
enseignant1.show()
- linha 10: cria-se um professor a partir de um dicionário que contém uma chave (sexo) que não pertence à classe [Enseignant]. Nesse caso, deveria ser lançada uma exceção;
Os resultados são os seguintes:
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. O script [fromdict_04]
O script [fromdict_04] é uma cópia do [fromdict_03], com uma única diferença:
# configura-se o aplicativo
import config
config = config.configure()
# o syspath está configurado — já é possível fazer as importações
from Enseignant import Enseignant
# um professor
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "albert", "âge": 56, "sexe": "M"}, silent=True)
enseignant1.show()
- linha 10: utilizou-se o parâmetro [silent=True] para indicar que, se uma chave do dicionário não for uma propriedade da classe [Enseignant], ela deve simplesmente ser ignorada. Nesse caso, nenhuma exceção será lançada;
Os resultados são os seguintes:
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. O método [BaseEntity.asdict]
13.2.2.1. Définition
O método [BaseEntity.asdict] retorna um dicionário cujas chaves são as propriedades do objeto:
def asdict(self, included_keys: list = None, excluded_keys: list =[]) -> dict:
# atributos do objeto
attributes = self.__dict__
# os novos atributos
new_attributes = {}
# percorremos os atributos
for key, value in attributes.items():
# se a chave for explicitamente solicitada
if included_keys and key in included_keys:
self.set_value(key, value, new_attributes)
# caso contrário, se a chave não for excluída
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)
# retorna-se o dicionário de atributos
return new_attributes
Comentários
- linha 1: a função [asdict] retorna o dicionário das propriedades do objeto;
- linha 1: [included_keys]: a lista de chaves a serem incluídas no dicionário;
- linha 1: [excluded_keys]: a lista de chaves a serem excluídas do dicionário;
- linha 3: a propriedade [self.__dict__] retorna o dicionário de propriedades do objeto. Os nomes das propriedades são as chaves e seus valores, os valores do dicionário. Um objeto pode conter referências a outros objetos. Nesse caso, os nomes das propriedades são prefixados pelo nome da classe à qual pertencem. Isso é algo que não queremos. Queremos as propriedades sem o prefixo;
- linha 3: é preciso entender aqui que, se a função [asdict] for executada dentro de uma classe derivada de [BaseEntity], a propriedade [self.__dict__] retorna o dicionário de propriedades do objeto derivado;
- linha 5: o dicionário que será construído;
- linha 7: percorremos os valores de [self.__dict__] na forma (chave, valor);
- linha 9: se a chave atual estiver na lista de chaves a serem incluídas, ela será adicionada ao dicionário [new_attributes] pela função [set_value], que descreveremos a seguir;
- linha 12: se o parâmetro [included_keys] não estiver presente, então o parâmetro [excluded_keys] é utilizado. Se a propriedade não fizer parte das propriedades a serem excluídas, ela é adicionada ao dicionário [new_attributes];
- linha 12: há várias maneiras de excluir uma propriedade do dicionário:
- ela foi definida no nível do atributo de classe [excluded_keys];
- ela foi definida na lista [excluded_keys] passada para a função [asdict];
- o parâmetro [included_keys] está presente e não inclui a propriedade;
- linha 15: retorna-se o dicionário [new_attributes]
A função [set_value] das linhas 10 e 13 é a seguinte:
@staticmethod
def set_value(key: str, value, new_attributes: dict):
# as chaves podem ter o formato __Class__key
match = re.match("^.*?__(.*?)$", key)
if match:
# anota-se a nova chave
newkey = match.groups()[0]
else:
# a chave permanece inalterada
newkey = key
# insere-se a nova chave no dicionário [new_attributes]
# transformando, se necessário, o valor associado em um dos tipos
# dicionário, lista, tipo simples
new_attributes[newkey] = BaseEntity.check_value(value)
Comentários
- linha 4: verifica-se se a chave tem o formato __Class_key. Esse é o formato que ela assume se pertencer a um objeto incluído no objeto principal. Nesse caso, deseja-se manter apenas a sequência [key];
- linha 7: mantém-se apenas a sequência que se segue aos dois últimos caracteres sublinhados da sequência;
- linhas 8-10: se a chave não tiver o formato __Class_key, ela é mantida como está;
- linhas 11-14: o valor associado à chave [newkey] é calculado pelo método estático [BaseEntity.check_value];
O método estático [BaseEntity.check_value] é o seguinte:
@staticmethod
def check_value(value):
# o valor pode ser do tipo BaseEntity, lista, dicionário ou um tipo simples
# o valor é uma instância de BaseEntity?
if isinstance(value, BaseEntity):
value2 = value.asdict()
# O valor é do tipo lista?
elif isinstance(value, list):
value2 = BaseEntity.list2list(value)
# O valor é do tipo dicionário?
elif isinstance(value, dict):
value2 = BaseEntity.dict2dict(value)
#é um tipo simples
else:
value2 = value
# retornamos o resultado
return value2
- linha 1: o método [check_value] é estático (método de classe e não de instância). Ele recebe como parâmetro o valor a ser associado a uma chave do dicionário:
- linha 17: se esse valor for de um tipo simples, ele permanece inalterado;
- linhas 5-6: se esse valor for do tipo BaseEntity, ele é substituído por seu dicionário. Nesse caso, ocorre uma chamada recursiva;
- linhas 8-9: se esse valor for uma lista, ele é substituído pelo valor [BaseEntity.list2list];
- linhas 11-12: se esse valor for um dicionário, ele é substituído pelo valor [BaseEntity.dict2dict];
O método estático [BaseEntity.list2list] é o seguinte:
@staticmethod
def list2list(liste: list) -> list:
# inspecionamos os elementos da lista
newlist = []
for value in liste:
newlist.append(BaseEntity.check_value(value))
# retornamos a nova lista
return newlist
- linha 2: o método recebe uma lista e retorna uma lista;
- linhas 5-6: cada valor da lista recebida como parâmetro é substituído pelo valor retornado pelo método estático [BaseEntity.check_value]. Portanto, trata-se de uma chamada recursiva. O método estático [BaseEntity.check_value] é chamado até que seu parâmetro [value] seja um tipo simples (não um tipo BaseEntity, lista ou dicionário);
O método estático [BaseEntity.dict2dict] é o seguinte:
@staticmethod
def dict2dict(dictionary: dict) -> dict:
# inspeciona-se os elementos do dicionário
newdict = {}
for key, value in dictionary.items():
newdict[key] = BaseEntity.check_value(value)
# retorna o novo dicionário
return newdict
- linha 2: o método recebe um dicionário e retorna um dicionário;
- linhas 5-6: cada valor do dicionário recebido como parâmetro é substituído pelo valor retornado pelo método estático [BaseEntity.check_value]. Portanto, trata-se de uma chamada recursiva. O método estático [BaseEntity.check_value] é chamado até que seu parâmetro [value] seja um tipo simples (não um tipo BaseEntity, nem uma lista nem um dicionário);
13.2.2.2. Exemples
O script [asdict_01] mostra diversas utilizações do método [asdict]:
# configura-se o aplicativo
import config
config = config.configure()
# o syspath está configurado — já é possível fazer as importações
from Enseignant import Enseignant
from BaseEntity import BaseEntity
# um professor
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)
# outro professor
enseignant2 = Enseignant().fromdict({"id": 2, "nom": "abélard", "prénom": "béatrice", "âge": 57})
print(enseignant2.asdict())
print(enseignant2.asdict(included_keys=["_Personne__nom"]))
# uma lista de entidades em uma entidade
Enseignant.excluded_keys = []
entity1 = BaseEntity()
enseignants = [enseignant1, enseignant2]
setattr(entity1, "enseignants", enseignants)
print(entity1.asdict())
# um dicionário de entidades dentro de uma entidade
matières = {"maths": enseignant1, "français": enseignant2}
setattr(entity1, "matières", matières)
print(entity1.asdict())
Os resultados da execução são os seguintes:
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
- a linha 4 mostra a vantagem do método [asdict] em relação ao uso da propriedade [__dict__]. As propriedades não apresentam o prefixo de sua classe. Isso facilita a exibição;
- há várias maneiras de utilizar o método [asdict]:
- se quisermos todas as propriedades: usamos o método [asdict] sem parâmetros;
- se quisermos apenas algumas propriedades:
- há mais propriedades a serem incluídas do que excluídas: utilizar-se-á apenas o parâmetro [excluded_keys];
- há menos propriedades a serem incluídas do que excluídas: utilizar-se-á apenas o parâmetro [included_keys];
13.2.3. O método [BaseEntity.asjson]
Este método permite obter a cadeia jSON a partir de um objeto [BaseEntity] ou derivado. Ele exibe a cadeia jSON do dicionário retornado pelo método [asdict]. Seu código é o seguinte:
def asjson(self, included_keys: list = None, excluded_keys: list = []) -> str:
# a string JSON
return json.dumps(self.asdict(included_keys=included_keys, excluded_keys=excluded_keys), ensure_ascii=False)
- linha 1: os parâmetros do método [asjson] são os mesmos do método [asdict];
Aqui está um exemplo (asjson_01) que utiliza esse método:
# configuramos o aplicativo
import config
config = config.configure()
# o syspath está configurado — já é possível fazer as importações
from Enseignant import Enseignant
from BaseEntity import BaseEntity
# um professor
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())
# outro professor
enseignant2 = Enseignant().fromdict({"id": 2, "nom": "abélard", "prénom": "béatrice", "âge": 57})
print(enseignant2.asjson())
print(enseignant2.asjson(included_keys=["_Personne__nom"]))
# uma lista de entidades em uma entidade
Enseignant.excluded_keys = []
entity1 = BaseEntity()
enseignants = [enseignant1, enseignant2]
setattr(entity1, "enseignants", enseignants)
print(entity1.asjson())
# um dicionário de entidades em uma entidade
matières = {"maths": enseignant1, "français": enseignant2}
setattr(entity1, "matières", matières)
print(entity1.asjson())
Os resultados são os seguintes:
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
O método [BaseEntity.__str__] utiliza o método [asjson] para exibir a identidade do objeto [BaseEntity] ou de um derivado:
# toString
def __str__(self) -> str:
return self.asjson()
13.2.4. O método [BaseEntity.fromjson]
O método [BaseEntity.fromjson] permite inicializar um objeto do tipo [BaseEntity] ou derivado a partir de um dicionário jSON. Seu código é o seguinte:
def fromjson(self, json_state: str, silent: bool = False):
# atualiza-se o status do objeto a partir da cadeia jSON
return self.fromdict(json.loads(json_state), silent=silent)
- linha 1: o método aceita dois parâmetros:
- [json_state]: o dicionário jSON que será usado para inicializar o objeto [BaseEntity];
- [silent]: para indicar se a presença no dicionário jSON de uma chave que não pode ser aceita como propriedade do objeto [BaseEntity] gera uma exceção (silent=False) ou é simplesmente ignorada (silent=True);
- linha 3: começa-se construindo o dicionário Python, que é uma representação do dicionário jSON, e, em seguida, utiliza-se o método [fromdict] para inicializar o objeto [BaseEntity] a partir desse dicionário Python;
Veja um exemplo (fromjson_01):
# configura-se o aplicativo
import config
config = config.configure()
# o syspath está configurado — é possível realizar as importações
from Enseignant import Enseignant
import json
# um professor
json1 = json.dumps({"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56})
enseignant1 = Enseignant().fromjson(json1)
enseignant1.show()
- linha 11: cria-se a string jSON a partir de um dicionário;
- linha 12: um objeto [Enseignant] é inicializado com essa string;
- linha 13: o professor é exibido;
Os resultados são os seguintes:
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. O script [main]
O script [main] resume os diferentes métodos encontrados:
# configura-se o aplicativo
import config
config = config.configure()
# o syspath está configurado — já é possível fazer as importações
from BaseEntity import BaseEntity
from MyException import MyException
# uma turma
class ChildEntity(BaseEntity):
# atributos excluídos do estado da 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})")
# configuração ChildEntity
ChildEntity.excluded_keys = []
# instância ChildEntity
child = ChildEntity().fromdict({"att1": 1, "att2": 2})
# atenção aos nomes das propriedades
# são esses nomes que são usados em [excluded_keys] e [included_keys]
print(child.__dict__)
# propriedades sem o prefixo da respectiva classe
print(child)
# instância ChildEntity
try:
child = ChildEntity().fromdict({"att1": 1, "att5": 5})
print(child)
except MyException as erreur:
print(erreur)
# instância ChildEntity
child = ChildEntity().fromdict({"att1": 1, "att2": 2, "att3": 3, "att4": 4})
print(child)
# exclusões de determinadas chaves do relatório de instâncias
ChildEntity.excluded_keys = ['att3']
print(child)
# uma chave é explicitamente excluída da exibição
# ela é adicionada às chaves excluídas globalmente no nível da classe
print(child.asdict(excluded_keys=["_ChildEntity__att1"]))
print(child.asjson(excluded_keys=["att2"]))
# importância da classe em relação ao dicionário
# ela pode verificar a validade de seu conteúdo
try:
child = ChildEntity().fromdict({"att1": 20})
except MyException as erreur:
print(erreur)
# instância ChildEntity
child1 = ChildEntity().fromdict({"att1": 1, "att2": 2, "att3": 3, "att4": 4})
# instância ChildEntity contendo outra instância ChildEntity
child2 = ChildEntity().fromdict({"att1": 10, "att2": 20, "att3": 30, "att4": child1})
print(child2)
# included_keys tem prioridade sobre excluded_keys, que são, portanto, ignoradas
ChildEntity.excluded_keys = ['_ChildEntity__att1', 'att2']
print(child.asdict(included_keys=["_ChildEntity__att1", "att3"], excluded_keys=["att3", "att4"]))
Os resultados da execução são os seguintes:
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
Preste atenção à linha 2 dos resultados: é a propriedade [ChildEntity.__dict__] (linha 38 do código) que nos permite saber os nomes das propriedades a serem incluídas nas listas [included_keys] e [excluded_keys]. Observe, ainda na linha 2 dos resultados, que, dependendo se a propriedade é definida dentro da classe por um getter/setter ou se foi criada da mesma forma que se criaria a chave de um dicionário, ela é ou não prefixada pelo nome da classe [ChildEntity].