Dataclasses, Enums and Abstract Classes
Less boilerplate, safer design.
Ava's Plant class has twelve lines, and most of them only store data. Every new class repeats the same init, repr and eq. Modern Python removes that boilerplate. Data classes write the boring methods for you. Enums give names to fixed choices. And abstract classes define rules that every subclass must follow.

- Too much repeated boilerplate
- Fixed choices deserve names
- Some classes must follow rules
Dataclasses
Import dataclass, and decorate your class. You only list the fields with their types. Python then writes init, repr, and eq for you. You can give defaults, and create ordered or frozen versions. Compare it with the earlier version, and notice how much shorter it is.
from dataclasses import dataclass
@dataclass
class Plant:
name: str
price: float
stock: int = 0
a = Plant("fern", 9.5, 12)
b = Plant("fern", 9.5, 12)
print(a)
print(a == b)
print(Plant("ivy", 7.0))Plant(name='fern', price=9.5, stock=12) True Plant(name='ivy', price=7.0, stock=0)
More dataclass features
With frozen equals True, instances cannot be changed, which makes them safe and hashable. The field function handles tricky defaults, like an empty list, because a plain list default would be shared. And you can add normal methods too. Dataclasses are the best way to define simple records.
from dataclasses import dataclass, field
@dataclass(frozen=True)
class Point:
x: int
y: int
@dataclass
class Basket:
items: list = field(default_factory=list)
def add(self, item):
self.items.append(item)
p = Point(1, 2)
print(p)
b = Basket()
b.add("fern")
print(b)
try:
p.x = 9
except Exception as e:
print(type(e).__name__)Point(x=1, y=2) Basket(items=['fern']) FrozenInstanceError
Enums: named choices
An enum is a fixed set of named values. Instead of using the strings, paid, and shipped, which are easy to mistype, you define them once. Nobody can invent a state by accident, and your editor can autocomplete them. Each member has a name and a value.
from enum import Enum
class Status(Enum):
NEW = 1
PAID = 2
SHIPPED = 3
s = Status.PAID
print(s)
print(s.name, s.value)
print(s == Status.PAID)
print([x.name for x in Status])Status.PAID PAID 2 True ['NEW', 'PAID', 'SHIPPED']
Abstract base classes
An abstract class defines a contract. Methods marked abstract must be implemented by every subclass, and you cannot create objects from the abstract class itself. Here every Payment must have a pay method. Card and Cash each provide their own. If a subclass forgets, Python refuses to create it.
from abc import ABC, abstractmethod
class Payment(ABC):
@abstractmethod
def pay(self, amount): ...
class Card(Payment):
def pay(self, amount):
return f"Card charged {amount}"
print(Card().pay(20))
try:
Payment()
except TypeError as e:
print("Cannot create:", "abstract" in str(e))Card charged 20 Cannot create: True
An Order dataclass
Your turn. Define a dataclass called Order, with a customer name, a total, and a paid flag, defaulting to False. Create one, and print it. Ready? Here is one solution. The decorator, three typed fields, one default, and you get a free readable print.
Define a dataclass Order(customer: str, total: float, paid: bool = False) and print an instance.
Show hint
@dataclass and annotated fields
Show solution
from dataclasses import dataclass
@dataclass
class Order:
customer: str
total: float
paid: bool = False
print(Order("Maya", 34.5))Order(customer='Maya', total=34.5, paid=False)
Summary
Well done. Dataclasses remove boilerplate, enums name fixed choices, and abstract classes enforce a contract. That completes our tour of object oriented Python. Next, we explore the standard library, starting with dates, randomness, and clever collections.
Key takeaways- @dataclass writes init, repr, eq
- Enum for fixed choices
- ABC + @abstractmethod for contracts