Um rápido tour sobre as classes de dados do Python 3.7
Tradução do post de autoria de Anthony Shaw, publicado originalmente no Hacker Noon. Muito obrigado a eles (e especialmente ao David Smooke) por autorizar a tradução. Obrigado também ao Guido Percú pela revisão da tradução.
Uma nova funcionalidade do Python 3.7 são as “classes de dados“ (em inglês, data classes). Classes de dados são uma forma de automatizar a geração de código para classes que guardam múltiplas propriedades.
Elas tem ainda a vantagem de utilizar as anotações de tipos do Python 3:
Classes de dados vem no novo módulo padrão do Python 3.7, dataclasses, e tem duas coisas importantes que você vai precisar saber.
- O decorador dataclass, para decorar uma classe de dados
- O método field, para configurar campos
Padrões dos métodos mágicos
Por padrão qualquer classe de dados vai implementar init, repr, str e eq para você.
O método init vai ter argumentos nomeados com as mesmas assinaturas de tipo da classe.
O método eq vai comparar os atributos em ordem.
Todos os campos são declarados no topo da classe e as anotações de tipo são obrigatórias.
Esse método init vai ter uma assinatura (field_a: int, field_b: str) -> None. Você pode conferir isso digitando print(inspect.signature(example.init)).
Indução de tipo
É importante destacar que as anotações de tipo são apenas dicas. Ou seja, passando tipos diferentes não faz o programa dar erro ou tentar convertê-lo.
Como a declaração dos tipos é obrigatória (caso contrário o campo é ignorado), se você não quiser especificar um tipo, use o tipo Any do módulo typing.
Mutabilidade
O decorador da classe de dados tem o argumento frozen, que é False por padrão. Se especificado, os campos serão “congelados”, ou seja, serão apenas para leitura e se eq for True, que é o padrão, então o método mágico hash será implementado e as instâncias do objeto vão ser hashable, assim você pode usá-las como chaves de dicionários ou em um set.
Campos personalizados
O tipo principal em uma classe de dados é o tipo Field, que pertence ao módulo dataclass.
Por padrão, apenas criando um atributo da classe já instanciará um Field como demonstrado nos exemplos anteriores.
Se você precisa de um comportamento personalizado, você pode usar a fábrica de campos do módulo dataclasses.
Os parâmetros para field() são:
default: Se fornecido, esse será o valor padrão para esse campo. Isso é necessário pois a chamada do próprio field substitui o valor padrão de um argumento posicional. default_factory: Um objeto que pode ser chamado sem argumentos que será chamado quando um valor padrão for necessário para esse campo. init: Incluído como um parâmetro para o método init. repr: Incluído na string retornada pelo método repr. compare: Incluído nos métodos de equidade e comparação (eq, gt, etc.). hash: Incluído no método hash.
Tem ainda um outro argumento, metadata que não está em uso por enquanto.
De forma similar aos argumentos nomeados, campos com valores padrão devem ser declarados por último.
Demonstrando a fábrica com valor padrão:
Processamento pós-init
Você pode declarar um método post_init que será executado após o init gerado automaticamente.
Herança
Herança acontece normalmente. Você precisa utilizar dataclass para a classe que herda e para a classe que serve como base.
No entanto, como você não pode declarar um campo sem valor padrão depois de declarar um campo que tem um valor padrão, você não pode misturar campos com e sem valor padro nas classe base e nas que herdam dela.
Esse exemplo quebra com TypeError: non-default argument ‘field_a’ follows default argument.
Isso é bem irritante e provavelmente vai fazer com que as pessoas não usem muito herança ou campos com valores padrão.
De toda forma, essa é uma ótima funcionalidade e eu provavelmente vou parar de usar attrs quando Python 3.7 for lançado.
Discussion in the ATmosphere