W3docs

Python Dataclasses

Aprenda Python dataclasses: o decorador @dataclass, defaults com field(), ordenação, imutabilidade e herança com exemplos práticos.

Uma dataclass é uma classe Python comum cujo código repetitivo — __init__, __repr__ e __eq__ — é gerado automaticamente pelo decorador @dataclass. O resultado é menos código, menos erros de digitação e classes imediatamente legíveis.

Este capítulo cobre:

  • Por que dataclasses existem e quando usá-las
  • O decorador @dataclass
  • Defaults de campos e o auxiliar field()
  • Controle de igualdade e ordenação
  • Dataclasses imutáveis com frozen=True
  • Lógica pós-inicialização com __post_init__
  • Herança com dataclasses
  • Dataclasses vs. NamedTuple vs. classes simples

Antes de ler este capítulo, certifique-se de estar familiarizado com classes e objetos Python e herança Python.

Por que Dataclasses?

Considere uma classe que armazena um produto em uma loja online. Sem dataclasses, você escreve as mesmas atribuições de atributo três vezes — uma vez em __init__, uma vez em __repr__ e uma vez em __eq__:

class Product:
    def __init__(self, name, price, stock):
        self.name = name
        self.price = price
        self.stock = stock

    def __repr__(self):
        return f"Product(name={self.name!r}, price={self.price}, stock={self.stock})"

    def __eq__(self, other):
        if not isinstance(other, Product):
            return NotImplemented
        return (self.name, self.price, self.stock) == (other.name, other.price, other.stock)

O decorador @dataclass gera tudo o acima a partir de uma única lista anotada de campos:

from dataclasses import dataclass

@dataclass
class Product:
    name: str
    price: float
    stock: int

Ambas as versões se comportam de forma idêntica. A versão com dataclass é mais curta, mais difícil de errar e comunica imediatamente que essa classe é principalmente um contêiner de dados.

O Decorador @dataclass

Importe dataclass do módulo da biblioteca padrão dataclasses e aplique-o à sua classe. Cada campo é declarado como uma variável de classe com anotação de tipo:

from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

p = Point(1.5, 2.0)
print(p)          # Point(x=1.5, y=2.0)
print(p.x)        # 1.5

p2 = Point(1.5, 2.0)
print(p == p2)    # True  — __eq__ compares field by field

O decorador gera:

MétodoO que faz
__init__Aceita cada campo como parâmetro e o atribui a self
__repr__Retorna uma string legível como Point(x=1.5, y=2.0)
__eq__Compara duas instâncias campo a campo

Anotações de tipo são obrigatórias, mas não verificadas em tempo de execução

Declarações de campo exigem uma anotação de tipo (x: float). Python não verifica o tipo em tempo de execução — você ainda pode passar uma string onde um float é esperado. A anotação é metadado usado por verificadores de tipo como mypy e pela própria maquinaria do dataclasses. Para validação de tipo em tempo de execução, consulte Python Type Hints.

Valores Padrão

Atribua um valor padrão diretamente no campo para torná-lo opcional em __init__:

from dataclasses import dataclass

@dataclass
class Config:
    host: str = "localhost"
    port: int = 8080
    debug: bool = False

c1 = Config()
print(c1)   # Config(host='localhost', port=8080, debug=False)

c2 = Config(host="example.com", port=443)
print(c2)   # Config(host='example.com', port=443, debug=False)

Campos com padrões devem aparecer depois de campos sem padrões — exatamente a mesma regra que se aplica a parâmetros de funções regulares.

Padrões mutáveis e field()

Não é possível usar um objeto mutável (uma lista, dict ou set) como valor padrão simples. Python compartilharia uma lista entre todas as instâncias, o que leva a bugs sutis:

from dataclasses import dataclass

# This raises a ValueError at class definition time:
# @dataclass
# class Bag:
#     items: list = []   # ValueError: mutable default is not allowed

Em vez disso, use field(default_factory=...) para criar um novo objeto para cada instância:

from dataclasses import dataclass, field

@dataclass
class Bag:
    items: list = field(default_factory=list)

b1 = Bag()
b2 = Bag()
b1.items.append("apple")

print(b1.items)   # ['apple']
print(b2.items)   # []  — b2 has its own separate list

default_factory aceita qualquer chamável sem argumentos, incluindo lambdas e suas próprias funções.

O Auxiliar field()

field() oferece controle detalhado sobre campos individuais. Seus parâmetros mais úteis são:

ParâmetroFinalidade
defaultUm valor padrão simples (somente escalar)
default_factoryUm chamável que produz o padrão
reprFalse para excluir este campo de __repr__
compareFalse para excluir este campo de __eq__ (e ordenação)
initFalse para excluir este campo de __init__
from dataclasses import dataclass, field
import time

@dataclass
class LogEntry:
    message: str
    level: str = "INFO"
    timestamp: float = field(default_factory=time.time, repr=False, compare=False)

entry = LogEntry("Server started")
print(entry)               # LogEntry(message='Server started', level='INFO')
# timestamp exists but is hidden from repr and ignored in comparisons
print(entry.timestamp > 0) # True

Ordenação

Por padrão, dataclasses suportam igualdade (==, !=) mas não ordenação (<, >, <=, >=). Habilite a ordenação passando order=True ao decorador:

from dataclasses import dataclass

@dataclass(order=True)
class Version:
    major: int
    minor: int
    patch: int

v1 = Version(1, 2, 0)
v2 = Version(1, 3, 0)
v3 = Version(1, 2, 0)

print(v1 < v2)    # True
print(v1 == v3)   # True
print(v2 > v1)    # True

versions = [Version(2, 0, 0), Version(1, 9, 1), Version(1, 2, 3)]
print(sorted(versions))
# [Version(major=1, minor=2, patch=3),
#  Version(major=1, minor=9, patch=1),
#  Version(major=2, minor=0, patch=0)]

Python gera os métodos de comparação comparando campos na ordem em que são declarados, no estilo de tupla. Você pode excluir um campo das comparações com field(compare=False).

Dataclasses Imutáveis com frozen=True

Passe frozen=True para tornar todos os campos somente leitura após a criação. Qualquer tentativa de alterar um campo gera um FrozenInstanceError:

from dataclasses import dataclass

@dataclass(frozen=True)
class Coordinate:
    lat: float
    lon: float

london = Coordinate(51.5074, -0.1278)
print(london)        # Coordinate(lat=51.5074, lon=-0.1278)

# london.lat = 0.0  # FrozenInstanceError: cannot assign to field 'lat'

Dataclasses congeladas também são hasheáveis (implementam __hash__), portanto você pode usá-las como chaves de dicionário ou membros de set:

from dataclasses import dataclass

@dataclass(frozen=True)
class Coordinate:
    lat: float
    lon: float

cities = {
    Coordinate(51.5074, -0.1278): "London",
    Coordinate(48.8566,  2.3522): "Paris",
}
print(cities[Coordinate(51.5074, -0.1278)])   # London

Dataclasses regulares (mutáveis) não são hasheáveis por padrão — Python define __hash__ como None quando __eq__ é definido sem frozen=True.

Lógica Pós-Inicialização com __post_init__

Às vezes você precisa derivar o valor de um campo a partir de outros campos, ou validar a entrada após a execução de __init__. Defina um método __post_init__ — ele é chamado automaticamente ao final do __init__ gerado:

from dataclasses import dataclass, field
import math

@dataclass
class Circle:
    radius: float

    def __post_init__(self):
        if self.radius <= 0:
            raise ValueError(f"radius must be positive, got {self.radius}")

    @property
    def area(self):
        return math.pi * self.radius ** 2

c = Circle(5)
print(round(c.area, 4))   # 78.5398

# Circle(-1)  # ValueError: radius must be positive, got -1

Você também pode calcular um campo derivado. Marque-o com field(init=False) para que não apareça em __init__, e então defina-o dentro de __post_init__:

from dataclasses import dataclass, field

@dataclass
class Rectangle:
    width: float
    height: float
    area: float = field(init=False, repr=True)

    def __post_init__(self):
        self.area = self.width * self.height

r = Rectangle(4, 6)
print(r)         # Rectangle(width=4, height=6, area=24)
print(r.area)    # 24

Herança com Dataclasses

Uma dataclass pode herdar de outra dataclass. O __init__ da classe filha inclui campos de ambas as classes — campos do pai primeiro, na ordem em que foram declarados:

from dataclasses import dataclass

@dataclass
class Animal:
    name: str
    age: int

@dataclass
class Dog(Animal):
    breed: str

rex = Dog(name="Rex", age=3, breed="Labrador")
print(rex)    # Dog(name='Rex', age=3, breed='Labrador')

Atenção: se uma classe pai tem um campo com padrão, todos os campos da classe filha também devem ter padrões. Esta é a mesma regra que se aplica a assinaturas de funções Python regulares — um parâmetro sem padrão não pode seguir um com padrão.

from dataclasses import dataclass

@dataclass
class Animal:
    name: str
    age: int = 0   # has a default

# @dataclass
# class Dog(Animal):
#     breed: str   # TypeError: non-default argument 'breed' follows default argument

Contorne isso dando ao campo da classe filha um padrão também, ou reestruturando a hierarquia para que campos com padrão venham por último.

Parâmetros do Decorador em Resumo

@dataclass(
    init=True,     # generate __init__       (default True)
    repr=True,     # generate __repr__       (default True)
    eq=True,       # generate __eq__         (default True)
    order=False,   # generate <, >, <=, >=   (default False)
    frozen=False,  # make fields immutable   (default False)
)
class MyClass:
    ...

Raramente você precisa alterar a maioria destes. Os mais comuns são order=True e frozen=True.

Funções Utilitárias

O módulo dataclasses também fornece três funções úteis:

fields()

Retorna uma tupla de objetos Field descrevendo cada campo da classe:

from dataclasses import dataclass, fields

@dataclass
class Point:
    x: float
    y: float

for f in fields(Point):
    print(f.name, f.type)
# x <class 'float'>
# y <class 'float'>

asdict()

Converte uma instância de dataclass em um dicionário simples (recursivamente):

from dataclasses import dataclass, asdict

@dataclass
class Address:
    street: str
    city: str

@dataclass
class Person:
    name: str
    address: Address

p = Person("Alice", Address("10 Downing St", "London"))
print(asdict(p))
# {'name': 'Alice', 'address': {'street': '10 Downing St', 'city': 'London'}}

Isso é útil ao serializar para JSON ou enviar dados para uma API.

astuple()

Converte para uma tupla (recursivamente):

from dataclasses import dataclass, astuple

@dataclass
class Point:
    x: float
    y: float

p = Point(3.0, 4.0)
print(astuple(p))   # (3.0, 4.0)

Dataclasses vs. NamedTuple vs. Classes Simples

RecursoClasse simplesNamedTupledataclass
__init__ automáticoNãoSimSim
__repr__ automáticoNãoSimSim
__eq__ automáticoNãoSim (por valor)Sim (por valor)
MutávelSimNãoSim (padrão)
HasheávelNão (se __eq__ definido)SimSomente com frozen=True
OrdenaçãoManualSimorder=True
HerançaSimLimitadaSim
Verificação isinstanceSimSim (também tuple)Sim
Desempacotamento (a, b = obj)NãoSimNão

Use uma dataclass quando:

  • Você quer dados mutáveis com imutabilidade opcional.
  • Você precisa de herança ou lógica pós-init.
  • Você quer controle detalhado de campos (field()).

Use NamedTuple quando:

  • Você quer um registro imutável que também se comporta como tupla (desempacotamento posicional, linhas CSV).
  • Você precisa de compatibilidade com código que espera tuplas.

Use uma classe simples quando:

  • A classe tem comportamento significativo e poucos dados simples.
  • Você precisa de um __init__ personalizado que não pode ser expresso através de __post_init__.

Armadilhas Comuns

Padrões mutáveis. Usar uma lista ou dict como valor padrão simples gera ValueError no momento da definição da classe. Sempre use field(default_factory=...).

Hashing. Dataclasses regulares não são hasheáveis. Se você precisar usá-las como chaves de dict ou em sets, use frozen=True ou passe unsafe_hash=True (raramente recomendado).

eq=False. Se você desabilitar a geração de igualdade (eq=False), Python volta à comparação por identidade (is), o que quase nunca é o desejado para objetos de dados.

Ordenação de padrões herdados. Se um campo do pai tem um padrão e um campo do filho não tem, Python gera um TypeError. Planeje cuidadosamente a ordenação dos campos na sua hierarquia.

Resumo

ConceitoO que faz
@dataclassGera __init__, __repr__, __eq__ automaticamente
field()Controle detalhado de campos: padrões, repr, compare, init
default_factoryFornece um padrão mutável fresco para cada instância
order=TrueAdiciona <, >, <=, >= com base na ordem dos campos
frozen=TrueTorna os campos somente leitura e a instância hasheável
__post_init__Executa após __init__ para validação ou campos derivados
fields()Retorna metadados sobre cada campo
asdict()Converte instância em um dict simples (recursivamente)
astuple()Converte instância em uma tupla simples (recursivamente)

Para tópicos relacionados, consulte classes e objetos Python, herança Python e classes base abstratas Python.

Prática

Prática
Which decorator do you use to create a dataclass in Python?
Which decorator do you use to create a dataclass in Python?
Was this page helpful?