Skip to content

面向对象与 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())
# 输出:Ada

self 是实例本身,实例属性应在初始化时建立清晰的默认状态。

实例方法、类属性与类方法 ​

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) 7

dataclass 自动生成初始化、表示和比较方法,适合主要存数据的对象。

组合而非继承 ​

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 会触发名称改写。
  • 继承表达“是一个”,组合表达“拥有一个”,不要为复用而盲目继承。

课后小问 ​

  1. dataclass 解决了什么问题? 答案:为以数据为主的类自动生成常见方法,减少样板代码。 解析:它仍是普通 Python 类,不会自动做类型校验、持久化或业务验证。

  2. 为什么可变字段要使用 default_factory? 答案:让每个实例在创建时得到独立的新容器。 解析:直接使用列表默认值会让所有实例共享同一个列表。

本节小结 ​

先用普通类表达行为,用 dataclass 表达数据;继承用于稳定的多态契约,组合用于替换实现和降低耦合。

快速回顾 ​

  • 对象初始化:__init__
  • 类级行为:classmethod、staticmethod
  • 数据类:@dataclass、field(default_factory=...)
  • 属性控制:@property
  • 复用选择:继承 vs 组合