Skip to content

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

Definimos agora duas classes que iremos utilizar regularmente daqui em diante.

Image

13.1. A classe MyException

A classe [MyException] (MyException.py) fornece uma classe de exceções própria:


# 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 através de um getter/setter;
  • linhas 23-24: verifica-se a validade do atributo [code]: tem de ser um número inteiro > 0;

13.2. A classe [BaseEntity]

A classe [BaseEntity] será a classe pai da maioria das classes que iremos criar para encapsular informações sobre um objeto. Daqui em diante, utilizaremos principalmente dois tipos de classes:

  • classes cujo único objetivo é encapsular, num único local, informações sobre um mesmo objeto. Estas não terão comportamentos (métodos) além de getters/setters e de uma função de visualização (__str__). Se houver N objetos para gerir, estas classes serão instanciadas N vezes. [BaseEntity] será a classe pai deste tipo de classes;
  • classes cuja função principal é encapsular métodos e muito pouca informação. Estas classes serão instanciadas apenas uma vez (singleton). A 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, disponibilizam-se os seguintes métodos:
    • [asdict]: devolve o dicionário de propriedades do objeto;
    • [fromdict]: cria um objeto a partir de um dicionário;
    • [asjson]: devolve a cadeia jSON do objeto, tal como faz a função [__str__];
    • [fromjson]: cria um objeto a partir da sua cadeia jSON;
  • a classe [BaseEntity] destina-se a ser derivada e não a ser utilizada tal como está;
  • linhas 22-25: a classe [BaseEntity] tem apenas uma propriedade, o inteiro [id]. Esta propriedade é o identificador do objeto. Na prática, é frequentemente útil poder diferenciar as instâncias de uma mesma classe. Fá-lo-emos com esta propriedade, única para cada instância. Além disso, os objetos provêm frequentemente de bases de dados onde são identificados por uma chave primária, geralmente um número inteiro. Nestes casos, [id] será a chave primária;
  • linhas 27-40: o setter da propriedade [id]. Verifica-se se é um número inteiro >=0. Se não for esse o caso, é lançada uma exceção do tipo [MyException] (linha 39);
  • linha 10: [excluded_keys] é um atributo de classe e não de instância. Assim, escrever-se-á [BaseEntity.excluded_keys]. Este atributo de classe é uma lista que contém as propriedades da classe que não participam nas conversões Objeto / Dicionário e Objeto / jSON;
  • linhas 12-16: [get_allowed_keys] devolve a lista das propriedades da classe. Numa conversão Dicionário -> Objeto ou jSON -> Objeto, só serão aceites as chaves presentes nesta lista. Cada classe derivada da classe [BaseEntity] terá de redefinir esta lista;

É importante compreender aqui que as propriedades e funções da classe [BaseEntity] estão acessíveis às classes derivadas de [BaseEntity]. Este é o ponto importante a compreender.

Vamos detalhar o código da classe [BaseEntity]. Trata-se de código bastante avançado. O leitor iniciante pode limitar-se a ler a função de cada método sem se debruçar sobre o seu 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):
        # o objeto está a ser atualizado
        # chaves autorizadas
        allowed_keys = self.__class__.get_allowed_keys()
        # a percorrer 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:
                # tenta-se atribuir o valor à chave
                # deixa-se que a 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 perante 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 esta função estática para nela declarar as suas propriedades;
  • linha 6: percorrem-se 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:
    • ignora-se;
    • lança-se uma exceção (linha 10). O programador indica o que pretende passando o parâmetro correto [silent] (linha 1). O valor por predefinição de [silent] faz com que seja lançada uma exceção se se tentar inicializar o objeto com uma propriedade que este não possui;
  • linha 14: se a chave fizer parte das propriedades do objeto, então é atribuída ao objeto [self] utilizando a função predefinida [setattr];
  • linha 16: a função devolve 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 cadeia está vazia?
            erreur = string.strip() == ''
        # resultado
        return not erreur

Nas linhas 3 a 11, define um método estático que retorna um valor booleano verdadeiro se o 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 próprio: nome próprio da pessoa
    # apelido: apelido 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 próprio não pode estar 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 próprio não pode estar 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-65: manteve-se o essencial da classe [Personne] já encontrada. As diferenças são as seguintes:
    • a classe já não tem um construtor;
    • a classe utiliza a exceção [MyException], exemplo na linha 65;
    • possui um método estático, [get_allowed_keys], linhas 17-20, que define a lista das suas propriedades. As propriedades específicas da classe [Personne] são adicionadas às da classe pai [BaseEntity];
    • 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 próprio: nome próprio da pessoa
    # apelido: apelido da pessoa
    # idade: idade da pessoa
    # disciplina: disciplina lecionada
    @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 cadeia 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] apresenta 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 ficheiro de configuração
    script_dir = os.path.dirname(os.path.abspath(__file__))

    # caminhos absolutos das pastas a incluir no syspath
    absolute_dependencies = [
        # a classe BaseEntity
        f"{script_dir}/entities",
    ]

    # atualização do syspath
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # efetua-se 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: é devolvido 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:


# a aplicação está a ser configurada
import config

config = config.configure()

# o syspath está configurado — já é possível efetuar 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 tal, utiliza-se o construtor por predefinição da classe para criar um objeto [Enseignant], ao qual se aplica o método [fromdict]. É importante compreender que, neste caso, o método [fromdict] executado é o da classe pai [BaseEntity]. Com efeito:
    • o método [fromdict] é primeiro procurado na classe [Enseignant]. Este não existe;
    • em seguida, é procurado na classe pai [Personne]. Não existe;
    • em seguida, é procurado na classe pai [BaseEntity]. Este existe;
  • linha 11: é apresentado 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:


# configuramos a aplicação
import config

config = config.configure()

# o syspath está configurado — já é possível efetuar 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 próprio em branco. Isto deve gerar uma exceção, uma vez que a classe [Personne] não aceita nomes próprios em branco. Este exemplo mostra a diferença entre um dicionário e um objeto. Este último pode verificar a validade das suas propriedades, ao passo que 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 a aplicação
import config

config = config.configure()

# o syspath está configurado — já é possível efetuar 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]. Deveria, então, 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 a aplicação
import config

config = config.configure()

# o syspath está configurado — já é possível efetuar 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], deve simplesmente ser ignorada. Neste caso, não será lançada qualquer 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_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] devolve 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] devolve o dicionário das propriedades do objeto;
  • linha 1: [included_keys]: a lista de chaves a incluir no dicionário;
  • linha 1: [excluded_keys]: a lista de chaves a excluir do dicionário;
  • linha 3: a propriedade [self.__dict__] devolve o dicionário de propriedades do objeto. Os nomes das propriedades são as chaves e os seus valores são 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 a que pertencem. Isso é algo que não queremos. Queremos as propriedades sem o seu prefixo;
  • linha 3: é importante compreender aqui que, se a função [asdict] for executada no interior de uma classe derivada de [BaseEntity], a propriedade [self.__dict__] devolve o dicionário de propriedades do objeto derivado;
  • linha 5: o dicionário que vamos construir;
  • linha 7: percorremos os valores de [self.__dict__] na forma (chave, valor);
  • linha 9: se a chave atual pertencer à lista de chaves a incluir, então é adicionada ao dicionário [new_attributes] pela função [set_value], que iremos descrever em breve;
  • linha 12: se o parâmetro [included_keys] não estiver presente, então é utilizado o parâmetro [excluded_keys]. Se a propriedade não fizer parte das propriedades a excluir, então é adicionada ao dicionário [new_attributes];
  • linha 12: existem várias formas de excluir uma propriedade do dicionário:
    • foi definida ao nível do atributo de classe [excluded_keys];
    • foi definida na lista [excluded_keys] passada à função [asdict];
    • o parâmetro [included_keys] está presente e não inclui a propriedade;
  • linha 15: devolve-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:
            # regista-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 num dos tipos
        # dict, list, tipo simples
        new_attributes[newkey] = BaseEntity.check_value(value)

Comentários

  • linha 4: verifica-se se a chave tem o formato __Class_key. É este o formato que assume se pertencer a um objeto incluído no objeto principal. Neste caso, pretende-se manter apenas a cadeia [key];
  • linha 7: mantém-se apenas a cadeia que se segue aos dois últimos caracteres sublinhados da cadeia;
  • linhas 8-10: se a chave não tiver o formato __Class_key, mantém-se tal 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 «list»?
        elif isinstance(value, list):
            value2 = BaseEntity.list2list(value)
        # O valor é do tipo dicionário?
        elif isinstance(value, dict):
            value2 = BaseEntity.dict2dict(value)
        #é de 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). Recebe como parâmetro o valor a associar a uma chave do dicionário:
    • linha 17: se este valor for de um tipo simples, permanece inalterado;
    • linhas 5-6: se este valor for do tipo BaseEntity, o valor é substituído pelo seu dicionário. Temos, então, uma chamada recursiva;
    • linhas 8-9: se este valor for uma lista, é substituído pelo valor [BaseEntity.list2list];
    • linhas 11-12: se este valor for um dicionário, é 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 devolve uma lista;
  • linhas 5-6: cada valor da lista recebida como parâmetro é substituído pelo valor devolvido pelo método estático [BaseEntity.check_value]. Trata-se, portanto, de uma chamada recursiva. O método estático [BaseEntity.check_value] é chamado até que o seu parâmetro [value] seja um tipo simples (não um tipo BaseEntity, nem uma lista nem um dicionário);

O método estático [BaseEntity.dict2dict] é o seguinte:


    @staticmethod
    def dict2dict(dictionary: dict) -> dict:
        # inspecionam-se os elementos do dicionário
        newdict = {}
        for key, value in dictionary.items():
            newdict[key] = BaseEntity.check_value(value)
        # retorna-se o novo dicionário
        return newdict
  • linha 2: o método recebe um dicionário e devolve um dicionário;
  • linhas 5-6: cada valor do dicionário recebido como parâmetro é substituído pelo valor devolvido pelo método estático [BaseEntity.check_value]. Trata-se, portanto, de uma chamada recursiva. O método estático [BaseEntity.check_value] é chamado até que o 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 várias utilizações do método [asdict]:


# configura-se a aplicação
import config
config = config.configure()

# o syspath está configurado — já é possível efetuar 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 numa entidade
Enseignant.excluded_keys = []
entity1 = BaseEntity()
enseignants = [enseignant1, enseignant2]
setattr(entity1, "enseignants", enseignants)
print(entity1.asdict())
# um dicionário de entidades numa 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 demonstra a vantagem do método [asdict] em relação à utilização da propriedade [__dict__]. As propriedades são apresentadas sem o prefixo da respetiva classe. Isto facilita a visualização;
  • existem várias formas de utilizar o método [asdict]:
    • se quisermos todas as propriedades: utilizamos o método [asdict] sem parâmetros;
    • se quisermos apenas algumas propriedades:
      • há mais propriedades a incluir do que a excluir: utilizar-se-á apenas o parâmetro [excluded_keys];
      • se houver menos propriedades a incluir do que a excluir: 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. Apresenta a cadeia jSON do dicionário devolvido pelo método [asdict]. O seu código é o seguinte:


def asjson(self, included_keys: list = None, excluded_keys: list = []) -> str:
        # a cadeia 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 do método [asdict];

Eis um exemplo (asjson_01) que utiliza este método:


# configura-se a aplicação
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 numa entidade
Enseignant.excluded_keys = []
entity1 = BaseEntity()
enseignants = [enseignant1, enseignant2]
setattr(entity1, "enseignants", enseignants)
print(entity1.asjson())
# um dicionário de entidades numa 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 apresentar a identidade do objeto [BaseEntity] ou de um objeto 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. O seu código é o seguinte:


def fromjson(self, json_state: str, silent: bool = False):
        # atualiza-se o estado 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á utilizado para inicializar o objeto [BaseEntity];
    • [silent]: para indicar se a presença no dicionário jSON de uma chave que não pode ser aceite como propriedade do objeto [BaseEntity] provoca uma exceção (silent=False) ou é simplesmente ignorada (silent=True);
  • linha 3: começa-se por construir 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;

Eis um exemplo (fromjson_01):


# configura-se a aplicação
import config

config = config.configure()

# o syspath está configurado — já é possível efetuar 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 cadeia jSON a partir de um dicionário;
  • linha 12: um objeto [Enseignant] é inicializado com esta cadeia;
  • linha 13: o professor é apresentado;

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 a aplicação
import config

config = config.configure()

# o syspath está configurado — já é possível efetuar 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 estes nomes que são utilizados em [excluded_keys] e [included_keys]
print(child.__dict__)
# propriedades sem o prefixo da respetiva 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 estado das instâncias
ChildEntity.excluded_keys = ['att3']
print(child)

# exclui-se explicitamente uma chave da visualização
# esta é adicionada às chaves excluídas globalmente ao 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
# pode verificar a validade do 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 que contém 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 então 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

Deve-se prestar atenção à linha 2 dos resultados: é a propriedade [ChildEntity.__dict__] (linha 38 do código) que nos permite conhecer os nomes das propriedades a incluir nas listas [included_keys] e [excluded_keys]. Note-se, ainda na linha 2 dos resultados, que, dependendo de a propriedade estar definida no interior da classe por um getter/setter ou de ter sido criada tal como se criaria a chave de um dicionário, ela é ou não prefixada pelo nome da classe [ChildEntity].