Skip to content

13. As classes genéricas [BaseEntity] e [MyException]

Definiremos agora duas classes que utilizaremos regularmente daqui em diante.

Image

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

Image

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].