Giao diện
Module dataclasses — Định nghĩa data class không boilerplate
Mỗi lần tạo một class chứa dữ liệu trong Python, bạn lại viết đi viết lại cùng một đoạn __init__, __repr__, __eq__ — đôi khi lên tới 30-50 dòng chỉ để khai báo 5 thuộc tính. Trong một hệ thống microservice với hàng chục DTO (Data Transfer Object), config object, và domain model, boilerplate này nhân lên thành hàng nghìn dòng code không mang giá trị logic nào.
Module dataclasses (có sẵn từ Python 3.7) giải quyết triệt để vấn đề này. Với một decorator @dataclass, Python tự sinh toàn bộ các dunder method cần thiết — bạn chỉ cần khai báo field và kiểu dữ liệu. Kết quả: class 50 dòng rút gọn còn 5 dòng, giữ nguyên type safety và khả năng mở rộng.
Bài viết này đi sâu vào mọi khía cạnh của dataclasses — từ cú pháp cơ bản đến frozen, slots, kế thừa, và so sánh trực tiếp với attrs và Pydantic để bạn chọn đúng công cụ cho từng bài toán production.
Bức tranh tư duy
Hãy hình dung @dataclass như tờ khai mẫu hành chính. Khi bạn đến cơ quan nhà nước, bạn không cần viết đơn từ đầu — bạn chỉ cần điền vào các ô trống trên mẫu đơn có sẵn. Tương tự, @dataclass là mẫu đơn: bạn khai báo tên field và kiểu dữ liệu, Python tự động điền vào các "thủ tục" (__init__, __repr__, __eq__, ...).
Bạn khai báo: Python tự sinh:
┌─────────────────────┐ ┌──────────────────────────┐
│ @dataclass │ │ __init__(self, ...) │
│ class User: │ ────▶ │ __repr__(self) │
│ name: str │ │ __eq__(self, other) │
│ email: str │ │ __hash__(self) (*) │
│ age: int = 0 │ │ __lt__, __le__,... (**) │
└─────────────────────┘ └──────────────────────────┘
(*) khi frozen=True
(**) khi order=TrueGiới hạn của phép so sánh: tờ khai mẫu chỉ xử lý quy trình chuẩn. Nếu bạn cần validation phức tạp, coercion kiểu dữ liệu, hoặc serialization tự động sang JSON — đó là lúc bạn cần thêm __post_init__, hoặc cân nhắc chuyển sang attrs / Pydantic.
Cốt lõi kỹ thuật
@dataclass cơ bản
Decorator @dataclass nhận nhiều tham số điều khiển hành vi sinh code:
python
from dataclasses import dataclass
@dataclass(
init=True, # Sinh __init__
repr=True, # Sinh __repr__
eq=True, # Sinh __eq__ và __ne__
order=False, # Sinh __lt__, __le__, __gt__, __ge__
unsafe_hash=False, # Sinh __hash__ (nguy hiểm với mutable)
frozen=False, # Bất biến — cấm gán lại attribute
match_args=True, # Hỗ trợ structural pattern matching (3.10+)
kw_only=False, # Mọi field đều keyword-only (3.10+)
slots=False, # Dùng __slots__ thay __dict__ (3.10+)
)
class User:
name: str
email: str
age: int = 0Ví dụ đơn giản nhất — chỉ cần 4 dòng để thay thế 25 dòng class truyền thống:
python
from dataclasses import dataclass
@dataclass
class Product:
name: str
price: float
quantity: int = 0
p = Product("Laptop", 25_000_000, 5)
print(p) # Product(name='Laptop', price=25000000, quantity=5)
p == Product("Laptop", 25_000_000, 5) # True — so sánh theo giá trịKhi bật order=True, Python sinh các phương thức so sánh dựa trên thứ tự khai báo field:
python
@dataclass(order=True)
class Priority:
level: int
name: str
tasks = [Priority(3, "Low"), Priority(1, "Critical"), Priority(2, "Medium")]
sorted(tasks)
# [Priority(level=1, name='Critical'),
# Priority(level=2, name='Medium'),
# Priority(level=3, name='Low')]field() — Kiểm soát từng thuộc tính
Hàm field() cho phép tinh chỉnh hành vi của từng field riêng lẻ:
python
from dataclasses import dataclass, field
from typing import List
@dataclass
class Team:
name: str
members: List[str] = field(default_factory=list) # Mutable default an toàn
_internal_id: str = field(repr=False, compare=False) # Ẩn khỏi repr và eq
created_by: str = field(kw_only=True) # Bắt buộc truyền keyword (3.10+)Bảng tham số field():
| Tham số | Mặc định | Mô tả |
|---|---|---|
default | MISSING | Giá trị mặc định |
default_factory | MISSING | Callable trả về default (cho mutable) |
init | True | Có xuất hiện trong __init__ không |
repr | True | Có xuất hiện trong __repr__ không |
compare | True | Tham gia __eq__ và order không |
hash | None | Tham gia __hash__ (None = theo compare) |
kw_only | False | Bắt buộc keyword-only (3.10+) |
metadata | None | Dict metadata tùy chỉnh |
Ví dụ field(init=False) cho computed field:
python
@dataclass
class Invoice:
items: List[float]
tax_rate: float = 0.1
total: float = field(init=False) # Tính sau, không truyền vào __init__
def __post_init__(self):
subtotal = sum(self.items)
self.total = subtotal * (1 + self.tax_rate)
inv = Invoice([100_000, 200_000, 50_000])
print(inv.total) # 385000.0post_init — Validation, normalization, computed fields
__post_init__ được gọi tự động sau __init__, là nơi lý tưởng để validate dữ liệu và tính toán field phụ thuộc:
python
from dataclasses import dataclass, field, InitVar
@dataclass
class Employee:
name: str
email: str
department: str
salary: float
password: InitVar[str] # Chỉ dùng trong __init__, không lưu trữ
password_hash: str = field(init=False, repr=False)
def __post_init__(self, password: str):
# Validation
if self.salary < 0:
raise ValueError(f"Lương không thể âm: {self.salary}")
if "@" not in self.email:
raise ValueError(f"Email không hợp lệ: {self.email}")
# Normalization
self.name = self.name.strip().title()
self.email = self.email.strip().lower()
# Computed field từ InitVar
import hashlib
self.password_hash = hashlib.sha256(password.encode()).hexdigest()
emp = Employee(" nguyen van a ", "NVA@Corp.com", "Engineering", 15_000_000, "s3cret")
print(emp.name) # "Nguyen Van A"
print(emp.email) # "nva@corp.com"
# password không tồn tại trên emp — chỉ password_hash được lưuInitVar[T] đặc biệt hữu ích khi bạn cần truyền dữ liệu vào constructor nhưng không muốn lưu nó thành attribute — ví dụ password, raw config string, hoặc temporary context.
frozen=True — Dataclass bất biến
frozen=True biến dataclass thành đối tượng bất biến (immutable). Mọi nỗ lực gán lại attribute đều ném FrozenInstanceError:
python
from dataclasses import dataclass, replace
@dataclass(frozen=True)
class Coordinate:
lat: float
lon: float
hanoi = Coordinate(21.0285, 105.8542)
# hanoi.lat = 10.0 # FrozenInstanceError!
# Tạo bản sao với giá trị mới bằng replace()
hcm = replace(hanoi, lat=10.8231, lon=106.6297)
print(hcm) # Coordinate(lat=10.8231, lon=106.6297)Ưu điểm quan trọng: frozen dataclass tự động hashable, có thể dùng làm dict key hoặc phần tử set:
python
@dataclass(frozen=True)
class CacheKey:
endpoint: str
params_hash: str
cache: dict[CacheKey, bytes] = {}
key = CacheKey("/api/users", "abc123")
cache[key] = b'{"users": []}' # Hợp lệ vì CacheKey hashableslots=True — Tối ưu bộ nhớ (Python 3.10+)
Khi slots=True, Python sinh __slots__ thay vì dùng __dict__, giảm đáng kể bộ nhớ trên mỗi instance:
python
from dataclasses import dataclass
import sys
@dataclass
class PointDict:
x: float
y: float
@dataclass(slots=True)
class PointSlots:
x: float
y: float
pd = PointDict(1.0, 2.0)
ps = PointSlots(1.0, 2.0)
print(sys.getsizeof(pd.__dict__)) # ~104 bytes
# ps không có __dict__
# hasattr(ps, '__dict__') # False
# Benchmark bộ nhớ với 100_000 instances:
# PointDict: ~16.8 MB
# PointSlots: ~8.8 MB (giảm ~48%)Lưu ý: slots=True không cho phép thêm attribute động. Nếu bạn cần flexibility, giữ slots=False.
Kế thừa (Inheritance)
Khi kế thừa dataclass, field của class cha đứng trước field class con trong __init__. Quy tắc quan trọng: field có default phải đứng sau field không có default.
python
from dataclasses import dataclass
@dataclass
class BaseConfig:
env: str
debug: bool = False
@dataclass
class DatabaseConfig(BaseConfig):
host: str = "localhost"
port: int = 5432
name: str = "app_db"
# __init__ nhận: env, debug=False, host="localhost", port=5432, name="app_db"
db = DatabaseConfig(env="production")Khi cả cha và con đều có __post_init__, bắt buộc gọi super().__post_init__():
python
@dataclass
class BaseModel:
id: int
def __post_init__(self):
if self.id <= 0:
raise ValueError(f"ID phải dương: {self.id}")
@dataclass
class UserModel(BaseModel):
name: str
email: str
def __post_init__(self):
super().__post_init__() # Gọi validation của BaseModel
self.name = self.name.strip()Thực chiến
Xây dựng hệ thống Configuration cho microservice
Bài toán thực tế: một microservice cần quản lý cấu hình database, cache, và logging — tất cả đều immutable sau khi load, có validation, và hỗ trợ serialization.
python
from dataclasses import dataclass, field, asdict, replace
from typing import Optional
import json
@dataclass(frozen=True)
class DatabaseConfig:
host: str
port: int = 5432
name: str = "app_db"
max_connections: int = 20
ssl_enabled: bool = True
def __post_init__(self):
if not (1 <= self.port <= 65535):
raise ValueError(f"Port ngoài phạm vi: {self.port}")
if self.max_connections < 1:
raise ValueError(f"max_connections phải >= 1: {self.max_connections}")
@property
def connection_string(self) -> str:
scheme = "postgresql+ssl" if self.ssl_enabled else "postgresql"
return f"{scheme}://{self.host}:{self.port}/{self.name}"
@dataclass(frozen=True)
class CacheConfig:
host: str = "localhost"
port: int = 6379
ttl_seconds: int = 300
max_memory_mb: int = 256
prefix: str = "app"
def __post_init__(self):
if self.ttl_seconds < 0:
raise ValueError(f"TTL không thể âm: {self.ttl_seconds}")
@dataclass(frozen=True)
class LoggingConfig:
level: str = "INFO"
format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
file_path: Optional[str] = None
def __post_init__(self):
valid_levels = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}
if self.level.upper() not in valid_levels:
raise ValueError(f"Log level không hợp lệ: {self.level}")
# frozen=True dùng object.__setattr__ để normalize
object.__setattr__(self, "level", self.level.upper())
@dataclass(frozen=True)
class AppConfig:
"""Configuration tổng hợp cho toàn bộ microservice."""
app_name: str
version: str
database: DatabaseConfig
cache: CacheConfig = field(default_factory=CacheConfig)
logging: LoggingConfig = field(default_factory=LoggingConfig)
allowed_origins: tuple[str, ...] = ("https://app.example.com",)
def to_json(self) -> str:
"""Serialize config ra JSON."""
return json.dumps(asdict(self), indent=2, ensure_ascii=False)
@classmethod
def from_json(cls, raw: str) -> "AppConfig":
"""Factory method: load config từ JSON string."""
data = json.loads(raw)
return cls(
app_name=data["app_name"],
version=data["version"],
database=DatabaseConfig(**data["database"]),
cache=CacheConfig(**data.get("cache", {})),
logging=LoggingConfig(**data.get("logging", {})),
allowed_origins=tuple(data.get("allowed_origins", [])),
)
def with_overrides(self, **kwargs) -> "AppConfig":
"""Tạo config mới với một số giá trị thay đổi."""
return replace(self, **kwargs)Sử dụng trong production:
python
# Load config
config = AppConfig(
app_name="order-service",
version="2.1.0",
database=DatabaseConfig(host="db.internal", port=5432, name="orders"),
)
print(config.database.connection_string)
# postgresql+ssl://db.internal:5432/orders
# Serialize
json_str = config.to_json()
# Override cho test environment
test_config = config.with_overrides(
database=DatabaseConfig(host="localhost", port=5433, name="orders_test", ssl_enabled=False),
logging=LoggingConfig(level="DEBUG"),
)
# Dùng làm cache key (hashable vì frozen=True)
config_cache: dict[AppConfig, dict] = {}
config_cache[config] = {"status": "loaded"}Sai lầm điển hình
1. Mutable default value — Bug kinh điển
python
# ❌ SAI: list mặc định chia sẻ giữa mọi instance
@dataclass
class Team:
name: str
members: list = [] # ValueError tại class creation!
# Python 3.7+ chặn lỗi này — nhưng dict, set vẫn cần cẩn thận
# ✅ ĐÚNG: Luôn dùng default_factory cho mutable
@dataclass
class Team:
name: str
members: list = field(default_factory=list)
metadata: dict = field(default_factory=dict)2. Quên super().post_init() khi kế thừa
python
@dataclass
class Base:
id: int
def __post_init__(self):
if self.id < 0:
raise ValueError("ID phải không âm")
# ❌ SAI: validation của Base bị bỏ qua hoàn toàn
@dataclass
class Child(Base):
name: str
def __post_init__(self):
self.name = self.name.strip()
Child(-1, "test") # Không raise! Bug tiềm ẩn
# ✅ ĐÚNG: Luôn gọi super().__post_init__()
@dataclass
class Child(Base):
name: str
def __post_init__(self):
super().__post_init__() # Chạy validation của Base trước
self.name = self.name.strip()3. unsafe_hash=True không hiểu hệ quả
python
# ❌ SAI: Hash thay đổi khi mutate → mất key trong dict
@dataclass(unsafe_hash=True)
class User:
name: str
scores: list = field(default_factory=list)
u = User("A")
cache = {u: "data"}
u.name = "B" # Hash thay đổi!
print(cache.get(u)) # None — mất dữ liệu
# ✅ ĐÚNG: Dùng frozen=True, hoặc loại mutable field khỏi hash
@dataclass(frozen=True)
class User:
name: str4. asdict với field không serializable
python
from dataclasses import dataclass, asdict
from datetime import datetime
import json
@dataclass
class Event:
name: str
timestamp: datetime
evt = Event("deploy", datetime(2024, 6, 15, 10, 30))
# ❌ SAI: datetime không JSON-serializable
# json.dumps(asdict(evt)) # TypeError!
# ✅ ĐÚNG: Custom serializer
def serialize_dataclass(obj) -> dict:
"""Chuyển dataclass sang dict JSON-safe."""
result = {}
for k, v in asdict(obj).items():
if isinstance(v, datetime):
result[k] = v.isoformat()
else:
result[k] = v
return result
json.dumps(serialize_dataclass(evt)) # OK5. slots=True với class cha không có slots
python
# ❌ SAI: Cha không slots, con có slots → con vẫn có __dict__ từ cha
@dataclass
class Base:
name: str
@dataclass(slots=True)
class Child(Base):
age: int
c = Child("A", 25)
c.dynamic = "oops" # Không lỗi! __dict__ từ Base vẫn tồn tại
# → Mất hoàn toàn lợi ích bộ nhớ của slots
# ✅ ĐÚNG: Cả chuỗi kế thừa đều dùng slots
@dataclass(slots=True)
class Base:
name: str
@dataclass(slots=True)
class Child(Base):
age: int
c = Child("A", 25)
# c.dynamic = "oops" # AttributeError — đúng hành vi mong đợiUnder the Hood
Cơ chế sinh code
Khi Python xử lý @dataclass, decorator đọc type annotation của class, tạo source code cho các method dưới dạng string, rồi exec() chúng vào namespace của class:
python
# Pseudo-code minh họa cơ chế bên trong
def dataclass(cls):
fields = extract_fields_from_annotations(cls)
# Sinh __init__ dưới dạng string rồi exec
init_code = f"def __init__(self, {', '.join(f.name for f in fields)}):\n"
for f in fields:
init_code += f" self.{f.name} = {f.name}\n"
exec(init_code, namespace)
cls.__init__ = namespace["__init__"]
# Tương tự cho __repr__, __eq__, ...
return clsBạn có thể kiểm tra metadata nội bộ qua __dataclass_fields__:
python
from dataclasses import dataclass, fields
@dataclass
class Server:
host: str
port: int = 8080
# Dict chứa Field objects
print(Server.__dataclass_fields__)
# {'host': Field(name='host', type=<class 'str'>, default=MISSING, ...),
# 'port': Field(name='port', type=<class 'int'>, default=8080, ...)}
# Hoặc dùng hàm fields() để lấy tuple
for f in fields(Server):
print(f"{f.name}: {f.type}, default={f.default}")Benchmark bộ nhớ
python
import sys
from dataclasses import dataclass
class RegularPoint:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
@dataclass
class DataclassPoint:
x: float
y: float
@dataclass(slots=True)
class SlotsPoint:
x: float
y: float
# Bộ nhớ trên mỗi instance (bytes, CPython 3.12):
# RegularPoint: ~152 (object + __dict__)
# DataclassPoint: ~152 (tương đương — vẫn dùng __dict__)
# SlotsPoint: ~56 (không __dict__, giảm ~63%)So sánh dataclass vs attrs vs Pydantic
| Đặc điểm | dataclass | attrs | Pydantic v2 |
|---|---|---|---|
| Có sẵn trong stdlib | ✅ | ❌ (cài thêm) | ❌ (cài thêm) |
| Validation tích hợp | ❌ (tự viết) | ✅ (validators) | ✅ (mạnh nhất) |
| Type coercion | ❌ | ❌ | ✅ |
| JSON serialization | Cơ bản (asdict) | cattrs | ✅ (tích hợp) |
| Hiệu năng khởi tạo | ⚡ Nhanh nhất | ⚡ Nhanh | 🐢 Chậm hơn (do validation) |
| Bộ nhớ (slots) | ✅ (3.10+) | ✅ | ✅ |
| Pattern matching | ✅ (match_args) | ✅ | ✅ |
| Hệ sinh thái plugin | Nhỏ | Trung bình | Lớn |
| Phù hợp cho | DTO đơn giản, config | Domain model phức tạp | API schema, form validation |
Khi nào chọn gì:
dataclass: Khi bạn cần DTO nhẹ, không muốn thêm dependency, validation đơn giản trong__post_init__attrs: Khi bạn cần validator mạnh hơn__post_init__, converter tích hợp, và vẫn muốn hiệu năng caoPydantic: Khi bạn xây API (FastAPI), cần type coercion tự động, JSON schema generation, và validation phức tạp
Checklist ghi nhớ
✅ Checklist triển khai
Khai báo cơ bản
- [ ] Luôn dùng
@dataclassthay vì viết__init__/__repr__/__eq__thủ công - [ ] Khai báo type annotation cho mọi field — đây là bắt buộc, không phải tùy chọn
- [ ] Đặt field có default value SAU field không có default
Mutable defaults & field()
- [ ] Dùng
field(default_factory=list)thay vì= []cho mutable default - [ ] Dùng
field(repr=False)cho field nhạy cảm (password hash, token) - [ ] Dùng
field(compare=False)cho metadata không tham gia so sánh logic
Validation & Post-init
- [ ] Đặt validation logic trong
__post_init__, không viết__init__riêng - [ ] Dùng
InitVar[T]cho dữ liệu chỉ cần lúc khởi tạo (password, raw input) - [ ] Luôn gọi
super().__post_init__()khi kế thừa dataclass có__post_init__
Immutability & Performance
- [ ] Dùng
frozen=Truecho config object, cache key, và mọi value object - [ ] Dùng
replace()thay vì mutate frozen dataclass - [ ] Dùng
slots=Truekhi tạo nhiều instance (>10,000) để tiết kiệm bộ nhớ - [ ] Đảm bảo toàn bộ chuỗi kế thừa đều có
slots=Truenếu dùng slots
Serialization
- [ ] Kiểm tra tất cả field đều JSON-serializable trước khi dùng
asdict()+json.dumps() - [ ] Viết custom serializer cho
datetime,Enum,Path, và các kiểu đặc biệt
Bài tập luyện tập
Bài 1: Money dataclass với validation
Tạo dataclass Money với các yêu cầu:
- Field
amount(Decimal) vàcurrency(str, 3 ký tự uppercase) - Frozen — không cho phép thay đổi sau khởi tạo
- Validate: amount >= 0, currency đúng 3 ký tự uppercase
- Method
__add__chỉ cộng được cùng currency
🧠 Quiz
Câu hỏi: Tại sao nên dùng Decimal thay vì float cho tiền tệ?
A. Decimal nhanh hơn float B. float có lỗi làm tròn nhị phân (0.1 + 0.2 ≠ 0.3) C. Decimal chiếm ít bộ nhớ hơn D. Python bắt buộc dùng Decimal cho số thập phân
Đáp án: B — float dùng biểu diễn IEEE 754 nhị phân, gây sai số tích lũy trong tính toán tài chính. Decimal dùng biểu diễn thập phân chính xác.
Lời giải tham khảo
python
from dataclasses import dataclass
from decimal import Decimal
@dataclass(frozen=True)
class Money:
amount: Decimal
currency: str
def __post_init__(self):
if not isinstance(self.amount, Decimal):
object.__setattr__(self, "amount", Decimal(str(self.amount)))
if self.amount < 0:
raise ValueError(f"Số tiền không thể âm: {self.amount}")
if len(self.currency) != 3 or not self.currency.isalpha() or not self.currency.isupper():
raise ValueError(f"Currency phải là 3 ký tự uppercase: {self.currency}")
def __add__(self, other: "Money") -> "Money":
if not isinstance(other, Money):
return NotImplemented
if self.currency != other.currency:
raise ValueError(f"Không thể cộng {self.currency} với {other.currency}")
return Money(self.amount + other.amount, self.currency)
def __str__(self) -> str:
return f"{self.amount:,.2f} {self.currency}"
# Kiểm thử
price = Money(Decimal("150000"), "VND")
tax = Money(Decimal("15000"), "VND")
total = price + tax
print(total) # 165,000.00 VND
# Frozen — không mutate được
# price.amount = Decimal("0") # FrozenInstanceError!
# Validation
# Money(Decimal("-1"), "VND") # ValueError
# Money(Decimal("100"), "usd") # ValueErrorBài 2: Immutable config với replace()
Xây dựng hệ thống config cho ứng dụng web với:
ServerConfig(host, port, workers, debug)— frozenAppConfig(name, version, server)— frozen, nested dataclass- Method
for_testing()trả về config mới vớidebug=True,workers=1 - Method
for_production()trả về config mới vớidebug=False,workers=8
🧠 Quiz
Câu hỏi: Khi dùng replace() trên frozen dataclass chứa nested frozen dataclass, nested object có được copy deep không?
A. Có — replace() luôn deep copy mọi thứ B. Không — replace() chỉ shallow copy, nested object được chia sẻ C. Tùy thuộc vào __copy__ method D. Python raise lỗi khi replace() trên nested frozen dataclass
Đáp án: B — replace() tạo shallow copy. Nếu bạn cần thay đổi nested object, phải replace() nested object trước rồi truyền vào outer replace().
Lời giải tham khảo
python
from dataclasses import dataclass, replace
@dataclass(frozen=True)
class ServerConfig:
host: str = "0.0.0.0"
port: int = 8000
workers: int = 4
debug: bool = False
def __post_init__(self):
if not (1 <= self.port <= 65535):
raise ValueError(f"Port không hợp lệ: {self.port}")
if self.workers < 1:
raise ValueError(f"Workers phải >= 1: {self.workers}")
@dataclass(frozen=True)
class AppConfig:
name: str
version: str
server: ServerConfig
def for_testing(self) -> "AppConfig":
"""Config dành cho môi trường test."""
new_server = replace(self.server, debug=True, workers=1)
return replace(self, server=new_server)
def for_production(self) -> "AppConfig":
"""Config dành cho môi trường production."""
new_server = replace(self.server, debug=False, workers=8)
return replace(self, server=new_server)
# Sử dụng
base = AppConfig(
name="order-api",
version="3.0.1",
server=ServerConfig(host="0.0.0.0", port=8000),
)
test_cfg = base.for_testing()
prod_cfg = base.for_production()
print(test_cfg.server.debug) # True
print(test_cfg.server.workers) # 1
print(prod_cfg.server.workers) # 8
# base không bị thay đổi (immutable)
print(base.server.debug) # False
print(base.server.workers) # 4Bài 3: So sánh hiệu năng thực tế
🧠 Quiz
Câu hỏi: Đoạn code nào tạo 100,000 instance nhanh nhất?
A.
python
@dataclass
class Point:
x: float
y: floatB.
python
@dataclass(slots=True)
class Point:
x: float
y: floatC.
python
@dataclass(frozen=True, slots=True)
class Point:
x: float
y: floatD. Cả ba gần như ngang nhau
Đáp án: B — slots=True nhanh nhất do bỏ overhead tạo __dict__. frozen=True thêm overhead kiểm tra immutability mỗi lần gán. Dataclass thường (A) chậm hơn B do tạo __dict__. Tuy nhiên sự khác biệt thường dưới 20% — chọn dựa trên tính đúng đắn trước, tối ưu sau.