摘要:class Person: def __init__(self, name, age, email): self.name = name self.age = age self.email = email def __repr__(self): return
今天我们学习 Day 13:数据类 @dataclass。这是 Python 3.7+ 中用于创建数据容器的强大装饰器。
传统类的繁琐写法:
class Person: def __init__(self, name, age, email): self.name = name self.age = age self.email = email def __repr__(self): return f"Person(name='{self.name}', age={self.age}, email='{self.email}')" def __eq__(self, other): if not isinstance(other, Person): return False return (self.name, self.age, self.email) == (other.name, other.age, other.email)person = Person("Alice", 25, "alice@example.com")print(person) # 需要手动实现 __repr__使用 @dataclass:
from dataclasses import dataclass@dataclassclass Person: name: str age: int email: strperson = Person("Alice", 25, "alice@example.com")print(person) # 自动生成 __repr__from dataclasses import dataclass@dataclassclass Point: x: float y: float@dataclassclass Rectangle: width: float height: float color: str = "white" # 默认值# 使用p1 = Point(1.5, 2.5)p2 = Point(1.5, 2.5)rect = Rectangle(10.0, 5.0)print(p1) # Point(x=1.5, y=2.5)print(p1 == p2) # True (自动实现 __eq__)print(rect) # Rectangle(width=10.0, height=5.0, color='white')field 函数的使用:
from dataclasses import dataclass, fieldfrom typing import List@dataclassclass Student: name: str age: int grades: List[float] = field(default_factory=list) student_id: int = field(init=False) # 不包含在 __init__ 中 _id_counter: int = field(default=0, init=False, repr=False) # 类变量,不显示 def __post_init__(self): """在 __init__ 后自动调用""" self._id_counter += 1 self.student_id = self._id_counter# 使用student = Student("Alice", 20, [85, 92, 78])print(student) # Student(name='Alice', age=20, grades=[85, 92, 78], student_id=1)from dataclasses import dataclass, field@dataclass(order=True) # 生成比较方法class Product: name: str = field(compare=False) # 不参与比较 price: float category: str = field(default="general", hash=True) # 参与哈希计算 def discount_price(self, discount: float) -> float: return self.price * (1 - discount)# 自动生成 __lt__, __le__, __gt__, __ge__products = [ Product("Laptop", 1000), Product("Phone", 500), Product("Tablet", 300)]sorted_products = sorted(products) # 按 price 排序for p in sorted_products: print(f"{p.name}: ${p.price}")from dataclasses import dataclass@dataclass(frozen=True) # 不可变,类似元组class ImmutablePoint: x: float y: floatpoint = ImmutablePoint(1.0, 2.0)print(point.x, point.y) # 可以读取# point.x = 3.0 # 会抛出 FrozenInstanceError继承:
@dataclassclass Person: name: str age: int@dataclassclass Employee(Person): employee_id: int department: str salary: float = 0.0emp = Employee("Alice", 25, 1001, "IT", 50000)print(emp) # Employee(name='Alice', age=25, employee_id=1001, department='IT', salary=50000.0)组合:
@dataclassclass Address: street: str city: str zip_code: str@dataclassclass Company: name: str address: Address employees: list = field(default_factory=list)address = Address("123 Main St", "New York", "10001")company = Company("Tech Corp", address)print(company)场景1:配置管理
from dataclasses import dataclass, fieldfrom typing import Optional, Dict, Any@dataclassclass DatabaseConfig: host: str = "localhost" port: int = 5432 username: str = "admin" password: str = field(repr=False) # 敏感信息,不显示 Database: str = "app_db" pool_size: int = 10 timeout: int = 30@dataclassclass AppConfig: app_name: str debug: bool = False database: DatabaseConfig = field(default_factory=DatabaseConfig) features: Dict[str, bool] = field(default_factory=dict) def get_feature_status(self, feature: str) -> bool: return self.features.get(feature, False)# 使用db_config = DatabaseConfig( host="db.example.com", password="secret123")app_config = AppConfig( app_name="MyApp", debug=True, database=db_config, features={"new_ui": True, "api_v2": False})print(app_config)场景2:API 响应数据模型
from dataclasses import dataclass, fieldfrom typing import List, Optionalfrom datetime import datetime@dataclassclass User: id: int username: str email: str created_at: datetime is_active: bool = True@dataclassclass PaginatedResponse: data: List[Any] total: int page: int per_page: int has_next: bool = field(init=False) has_prev: bool = field(init=False) def __post_init__(self): self.has_next = (self.page * self.per_page) 1 def to_dict(self) -> dict: return { "data": self.data, "pagination": { "total": self.total, "page": self.page, "per_page": self.per_page, "has_next": self.has_next, "has_prev": self.has_prev } }# 模拟 API 响应users = [ User(1, "alice", "alice@example.com", datetime.now), User(2, "bob", "bob@example.com", datetime.now)]response = PaginatedResponse( data=[user.__dict__ for user in users], total=100, page=1, per_page=10)print(response.to_dict)场景3:科学计算数据容器
from dataclasses import dataclass, fieldfrom typing import Tupleimport math@dataclassclass Vector3D: x: float y: float z: float def magnitude(self) -> float: return math.sqrt(self.x**2 + self.y**2 + self.z**2) def dot(self, other: 'Vector3D') -> float: return self.x * other.x + self.y * other.y + self.z * other.z def cross(self, other: 'Vector3D') -> 'Vector3D': return Vector3D( self.y * other.z - self.z * other.y, self.z * other.x - self.x * other.z, self.x * other.y - self.y * other.x ) def __add__(self, other: 'Vector3D') -> 'Vector3D': return Vector3D(self.x + other.x, self.y + other.y, self.z + other.z) def __mul__(self, scalar: float) -> 'Vector3D': return Vector3D(self.x * scalar, self.y * scalar, self.z * scalar)@dataclassclass Particle: position: Vector3D velocity: Vector3D mass: float charge: float = 0.0 def kinetic_energy(self) -> float: return 0.5 * self.mass * self.velocity.magnitude ** 2# 使用v1 = Vector3D(1, 2, 3)v2 = Vector3D(4, 5, 6)print(f"向量和: {v1 + v2}")print(f"点积: {v1.dot(v2)}")print(f"模长: {v1.magnitude:.2f}")particle = Particle(Vector3D(0, 0, 0), Vector3D(10, 0, 0), 2.0)print(f"动能: {particle.kinetic_energy:.2f}")技巧1:自定义字符串表示
@dataclassclass CustomRepr: name: str value: int def __str__(self): return f"CustomRepr: {self.name} ({self.value})" def __repr__(self): return f""obj = CustomRepr("test", 42)print(str(obj)) # CustomRepr: test (42)print(repr(obj)) #技巧2:数据验证
from dataclasses import dataclass, fieldfrom typing import Optional@dataclassclass ValidatedPerson: name: str age: int email: Optional[str] = None def __post_init__(self): self._validate def _validate(self): if self.age技巧3:json 序列化
from dataclasses import dataclass, asdictfrom datetime import datetimeimport json@dataclassclass User: id: int username: str created_at: datetime def to_json(self) -> str: data = asdict(self) data['created_at'] = self.created_at.isoformat return json.dumps(data) @classmethod def from_json(cls, json_str: str) -> 'User': data = json.loads(json_str) data['created_at'] = datetime.fromisoformat(data['created_at']) return cls(**data)# 使用user = User(1, "alice", datetime.now)json_str = user.to_jsonprint(f"JSON: {json_str}")new_user = User.from_json(json_str)print(f"从JSON恢复: {new_user}") 今日练习练习1:创建基础数据类
# 创建一个 Book 数据类,包含以下字段:# title: str, author: str, year: int, price: float, in_stock: bool = True# 添加计算属性:is_antique (出版超过50年)# 你的代码 here练习2:实现购物车系统
# 创建购物车系统,包含 Product 和 ShoppingCart 数据类# Product: name, price, category# ShoppingCart: items (List[Product]), 添加计算总价的方法# 你的代码 here练习3:配置验证系统
# 创建带验证的配置类,验证端口范围、URL格式等# 你的代码 here练习答案:
# 练习1答案:from dataclasses import dataclassfrom datetime import datetime@dataclassclass Book: title: str author: str year: int price: float in_stock: bool = True @property def is_antique(self) -> bool: current_year = datetime.now.year return current_year - self.year > 50# 测试book = Book("Python编程", "John Doe", 1995, 29.99)print(f"古董书: {book.is_antique}")# 练习2答案:from dataclasses import dataclass, fieldfrom typing import List@dataclassclass Product: name: str price: float category: str = "general"@dataclassclass ShoppingCart: items: List[Product] = field(default_factory=list) def add_item(self, product: Product): self.items.append(product) def total_price(self) -> float: return sum(item.price for item in self.items) def items_by_category(self) -> dict: categories = {} for item in self.items: if item.category not in categories: categories[item.category] = categories[item.category].append(item) return categories# 测试cart = ShoppingCartcart.add_item(Product("书", 25.0, "教育"))cart.add_item(Product("笔", 2.5, "文具"))print(f"总价: {cart.total_price}")print(f"按分类: {cart.items_by_category}")# 练习3答案:from dataclasses import dataclassfrom typing import Optionalimport re@dataclassclass ValidatedConfig: app_name: str port: int database_url: Optional[str] = None def __post_init__(self): self._validate def _validate(self): if not 1024 主要用于数据容器类合理使用 field 进行高级配置利用 __post_init__ 进行数据验证和初始化保持数据类的简单性,复杂逻辑放在方法中来源:晓啸教育