面向对象与 dataclass
学习目标
- 定义类、实例属性和方法。
- 理解继承、组合、属性和特殊方法。
- 用
dataclass简化数据对象,同时知道它不是数据库模型。
核心知识点
类描述对象的状态和行为,实例保存数据。Python 的继承是可选的复用工具,组合(对象拥有另一个对象)通常更灵活。双下划线方法是协议入口,如 __init__、__repr__、__len__。
常用用法
class 与 __init__
python
class User:
def __init__(self, name: str, active: bool = True) -> None:
self.name = name
self.active = active
def label(self) -> str:
return self.name if self.active else f"{self.name}(停用)"
user = User("Ada")
print(user.label())
# 输出:Adaself 是实例本身,实例属性应在初始化时建立清晰的默认状态。
实例方法、类属性与类方法
python
class Counter:
created = 0
def __init__(self) -> None:
type(self).created += 1
@classmethod
def total(cls) -> int:
return cls.created
Counter()
Counter()
print(Counter.total())
# 输出:2类属性由类和实例共享;classmethod 收到类对象,适合替代构造器或访问类级状态。
staticmethod
python
class TextTools:
@staticmethod
def normalize(value: str) -> str:
return value.strip().lower()
print(TextTools.normalize(" Python "))
# 输出:python静态方法不自动接收 self 或 cls;如果函数与类概念无关,放在模块函数中通常更直接。
继承与 super()
python
class Animal:
def speak(self) -> str:
return "..."
class Dog(Animal):
def speak(self) -> str:
return f"{super().speak()} woof"
print(Dog().speak())
# 输出:... woof重写方法时使用 super() 协作父类行为,不要硬编码父类名称。
属性 @property
python
class Celsius:
def __init__(self, value: float) -> None:
self._value = value
@property
def value(self) -> float:
return self._value
@value.setter
def value(self, new_value: float) -> None:
if new_value < -273.15:
raise ValueError("temperature below absolute zero")
self._value = new_value
temperature = Celsius(20)
temperature.value = 25
print(temperature.value)
# 输出:25属性把验证逻辑隐藏在类似字段的接口后,避免调用者直接破坏不变量。
dataclass
python
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int = 0
point = Point(3, 4)
print(point, point.x + point.y)
# 输出:Point(x=3, y=4) 7dataclass 自动生成初始化、表示和比较方法,适合主要存数据的对象。
组合而非继承
python
class Engine:
def start(self) -> str:
return "engine started"
class Car:
def __init__(self, engine: Engine) -> None:
self.engine = engine
def start(self) -> str:
return self.engine.start()
print(Car(Engine()).start())
# 输出:engine started组合让 Car 依赖一个可替换的引擎对象,测试时更容易注入假实现。
不常用但需要知道
__repr__ 与 __len__
python
class Playlist:
def __init__(self, songs: list[str]) -> None:
self.songs = songs
def __repr__(self) -> str:
return f"Playlist({self.songs!r})"
def __len__(self) -> int:
return len(self.songs)
playlist = Playlist(["intro", "theme"])
print(repr(playlist), len(playlist))
# 输出:Playlist(['intro', 'theme']) 2特殊方法接入内置协议,优先保持返回值符合 Python 约定。
dataclass 的 field(default_factory=...)
python
from dataclasses import dataclass, field
@dataclass
class Cart:
items: list[str] = field(default_factory=list)
first, second = Cart(), Cart()
first.items.append("book")
print(first.items, second.items)
# 输出:['book'] []可变字段必须用 default_factory,不能直接写 items: list[str] = []。
抽象基类
python
from abc import ABC, abstractmethod
class Formatter(ABC):
@abstractmethod
def format(self, value: str) -> str:
raise NotImplementedError
print(Formatter.__abstractmethods__)
# 输出:frozenset({'format'})抽象基类适合需要显式契约和运行时阻止不完整实例化的框架代码。
易混点
- 类属性与实例属性的查找顺序不同,避免用实例意外覆盖共享配置。
@staticmethod不会自动传入实例;普通实例方法需要self。dataclass(frozen=True)只限制字段赋值,不代表嵌套列表深度不可变。- 私有约定
_name不是强制访问控制,__name会触发名称改写。 - 继承表达“是一个”,组合表达“拥有一个”,不要为复用而盲目继承。
课后小问
dataclass解决了什么问题? 答案:为以数据为主的类自动生成常见方法,减少样板代码。 解析:它仍是普通 Python 类,不会自动做类型校验、持久化或业务验证。为什么可变字段要使用
default_factory? 答案:让每个实例在创建时得到独立的新容器。 解析:直接使用列表默认值会让所有实例共享同一个列表。
本节小结
先用普通类表达行为,用 dataclass 表达数据;继承用于稳定的多态契约,组合用于替换实现和降低耦合。
快速回顾
- 对象初始化:
__init__ - 类级行为:
classmethod、staticmethod - 数据类:
@dataclass、field(default_factory=...) - 属性控制:
@property - 复用选择:继承 vs 组合
阿源的知识库