O que são classes de dados e como eles são diferentes das classes comuns?
Com PEP 557 As classes de dados são introduzidas na biblioteca padrão python.
eles fazem uso do decorador {[[0]} e eles são supostos ser "mutable namedtuples with default", mas eu realmente não tenho certeza se eu entendo o que isso realmente significa e como eles são diferentes de classes comuns.
o que são exactamente as classes de dados python e quando é melhor usá-las?
4 answers
As classes de dados são apenas classes regulares que são orientadas para o estado de armazenamento, mais do que conter um monte de lógica. Cada vez que você cria uma classe que consiste principalmente de atributos você fez uma classe de dados.
O que o módulo dataclasses
faz é tornar mais fácil criar classes de dados. Toma conta de muita caldeira para ti.
Isto é especialmente importante quando a sua classe de dados tem de ser hashable; isto requer um método __hash__
assim como um método __eq__
. Se adiciona um método personalizado __repr__
para facilitar a depuração, que pode tornar-se bastante descritivo:
class InventoryItem:
'''Class for keeping track of an item in inventory.'''
name: str
unit_price: float
quantity_on_hand: int = 0
def __init__(
self,
name: str,
unit_price: float,
quantity_on_hand: int = 0
) -> None:
self.name = name
self.unit_price = unit_price
self.quantity_on_hand = quantity_on_hand
def total_cost(self) -> float:
return self.unit_price * self.quantity_on_hand
def __repr__(self) -> str:
return (
'InventoryItem('
f'name={self.name!r}, unit_price={self.unit_price!r}, '
f'quantity_on_hand={self.quantity_on_hand!r})'
def __hash__(self) -> int:
return hash(self.name) ^ hash(self.unit_price) ^ hash(self.quantity_on_hand)
def __eq__(self, other) -> bool:
if not isinstance(other, InventoryItem):
return NotImplemented
return (
(self.name, self.unit_price, self.quantity_on_hand) ==
(other.name, other.unit_price, other.quantity_on_hand))
Com {[2] } pode reduzi-lo a:
from dataclasses import dataclass
@dataclass(hash=True)
class InventoryItem:
'''Class for keeping track of an item in inventory.'''
name: str
unit_price: float
quantity_on_hand: int = 0
def total_cost(self) -> float:
return self.unit_price * self.quantity_on_hand
O mesmo decorador de classe também pode gerar métodos de comparação(__lt__
, __gt__
, etc.) and handle immutability.
namedtuple
classes também são classes de dados, mas são imutáveis por padrão (bem como sendo seqüências). {[[2]} são muito mais flexíveis a este respeito, e podem ser facilmente estruturados de modo que possam {[[30]} preencher o mesmo papel que um namedtuple
classe
attrs
projecto , que pode fazer ainda mais (incluindo fendas, validadores, conversores, metadados, etc.).
Se você quiser ver alguns exemplos, recentemente, usei dataclasses
para vários dos meus 2017 Advento do Código soluções consulte as soluções para dia 7, dia 8, dia 11 e dia 20.
Se quiser usar o módulo dataclasses
nas versões Python backported (necessita de 3.6) ou utilizar o projecto attrs
acima mencionado.
Da especificação PEP:
É fornecido um decorador de classe que inspecciona uma definição de classe para variáveis com anotações de tipo definidas em PEP 526, " sintaxe para Variable Annotations". Neste documento, tais variáveis são chamadas campo. Usando estes campos, o decorador adiciona o método gerado definições para a classe de suporte à inicialização de instância, um repr, métodos de comparação e, facultativamente, outros métodos descritos no Secção de especificações. Tal classe é chamada de classe de dados, mas não há nada de especial na aula: o decorador acrescenta métodos gerados para a classe e retorna a mesma classe que era dado.
O gerador {[[0]} adiciona métodos à classe que se definiria de outra forma como __repr__
, __init__
, __lt__
, e __gt__
Panorâmica
A questão foi abordada. No entanto, esta resposta acrescenta alguns exemplos práticos para ajudar na compreensão básica dos dataclasses.O que são exactamente as classes de dados python e quando é melhor usá-las?
- geradores de código : gerar código boilerplate; você pode optar por implementar métodos especiais numa classe regular ou ter um dataclass implementá-los automaticamente.
-
dados containers : structures that hold data (e.g. tuplas and dicts), often with tracked, attribute access such as classes,
namedtuple
and others.
Eis o que esta última frase significa:"mutable namedtuples with default"
- mutável : por omissão, os atributos dataclass podem ser reatribuídos. Você pode opcionalmente torná-los imutáveis (veja exemplos abaixo).
- Tem pontilhado, atributo acesso como uma aula normal.
- predefinição : você pode atribuir valores predefinidos aos atributos
Em comparação com as classes comuns, economiza-se principalmente na escrita do Código boilerplate.
Exemplos
Aqui está uma rápida visão geral das funcionalidades do dataclass com exemplos.
O que tens
[[25]} Aqui estão as funcionalidades que você obtém por padrão a partir de dataclasses.Atributos + Representação + Comparação
imp dataclasses
@dataclasses.dataclass
#@dataclasses.dataclass() # alternative
class Color:
r : int = 0
g : int = 0
b : int = 0
Os seguintes valores por omissão estão definidos:
@dataclasses.dataclass(init=True, repr=True, eq=True)
O que podes ligar
Algumas funcionalidades adicionais estão disponíveis se a palavra-chave apropriada for True
.
ordem
@dataclasses.dataclass(order=True)
class Color:
r : int = 0
g : int = 0
b : int = 0
Os métodos de encomenda são implementados (sobrecarga < > <= >=
), de forma semelhante à functools.total_ordering
com testes de igualdade mais fortes.
Hashable, Mutable
@dataclasses.dataclass(unsafe_hash=True) # override base `__hash__`
class Color:
...
Embora o objecto seja potencialmente mutável (possivelmente indesejável), um hash é implementado.
Hashable, Imutável
@dataclasses.dataclass(frozen=True) # `eq=True` (default) to be immutable
class Color:
...
Um hash é implementado e mudar o objecto ou atribuir atributos é proibido.
Globalmente, se unsafe_hash=True
ou frozen=True
for definido, o objecto é hashable.
Ver também a tabela lógica original sobre detalhes.
O que não percebesPara obter as seguintes características, os métodos especiais devem ser manualmente implementado:
Impacável
@dataclasses.dataclass
class Color:
r : int = 0
g : int = 0
b : int = 0
def __iter__(self):
yield from dataclasses.astuple(self)
optimização
@dataclasses.dataclass
class SlottedColor:
__slots__ = ["r", "b", "g"]
r : int
g : int
b : int
O tamanho do objecto está agora reduzido:
>>> imp sys
>>> sys.getsizeof(Color)
1056
>>> sys.getsizeof(SlottedColor)
888
Em algumas circunstâncias, {[19] } também melhora a velocidade de criação de instância e acesso de atributos. Além disso, os 'slots' não permitem atribuições predefinidas; caso contrário, um ValueError
é elevado.
Veja mais em slots neste postno blog .
Tabela-Resumo
+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
| Feature | Keyword | Example | Implement in a Class |
+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
| Attributes | init | Color().r -> 0 | __init__ |
| Representation | repr | Color() -> Color(r=0, g=0, b=0) | __repr__ |
| Comparision* | eq | Color() == Color(0, 0, 0) -> True | __eq__ |
| | | | |
| Order | order | sorted([Color(0, 50, 0), Color()]) -> ... | __lt__, __le__, __gt__, __ge__ |
| Hashing | unsafe_hash/frozen | {Color(), {Color()}} -> {Color(r=0, g=0, b=0)} | __hash__ |
| Immutable | frozen + eq | Color().r = 10 -> TypeError | __setattr__, __delattr__ |
| | | | |
| Unpackable+ | - | r, g, b = Color() | __iter__ |
| Optimization+ | - | sys.getsizeof(SlottedColor) -> 888 | __slots__ |
+----------------------+----------------------+----------------------------------------------------+-----------------------------------------+
+estes métodos não são gerados automaticamente e requerem implementação manual em um dataclass.
*__ne__
não está implementado.
Características adicionais
Pós-inicialização
@dataclasses.dataclass
class RGBA:
r : int = 0
g : int = 0
b : int = 0
a : float = 1.0
def __post_init__(self):
self.a : int = int(self.a * 255)
RGBA(127, 0, 255, 0.5)
# RGBA(r=127, g=0, b=255, a=127)
Herança
@dataclasses.dataclass
class RGBA(Color):
a : int = 0
Conversões
Converta um dataclass para uma tupla ou uma dict, recursivamente:
>>> dataclasses.astuple(Color(128, 0, 255))
(128, 0, 255)
>>> dataclasses.asdict(Color(128, 0, 255))
{r: 128, g: 0, b: 255}
Limitações
- Falta de mecanismos para lidar com argumentos de starry
Referências
- R. Hettinger's talk on Dataclasses: the code generator to end all code generators
- T. Hunner's talk on classes mais fáceis: classes Python sem todo o Cruft Documentação De Python sobre os detalhes da lavagem
- O guia real do Python em O Guia final das Classes de dados em Python 3.7 [[32]}A. Shaw's post no blog on a brief tour of Python 3.7 data classes
- o repositório de github de E. Smith em dataclasses