Python 类与面向对象基础

AI 辅助生成本文由 AI 起草并经人工核查,示例均标明软件版本;发现错误请以官方文档为准。

Python 3.13

当数据和处理数据的逻辑需要绑在一起时,就该用类。这一篇讲类的正确写法,以及 Python 特有的魔术方法。

环境准备

示例在 Python 3.13 下验证,无需第三方依赖。

定义类

class User:
    def __init__(self, name: str, age: int = 0):
        self.name = name        # 实例属性
        self.age = age

    def greet(self) -> str:
        return f"你好,我是 {self.name}"

u = User("小明", 18)
u.greet()        # '你好,我是 小明'
  • __init__ 是初始化方法(不是构造函数本体,对象由 __new__ 创建)。
  • self 指当前实例,方法第一个参数必须是它(名字只是约定)。
  • 调用 u.greet() 等价于 User.greet(u)。

实例属性 vs 类属性

class Counter:
    total = 0                 # 类属性,所有实例共享

    def __init__(self):
        self.count = 0        # 实例属性,每个实例一份
        Counter.total += 1

类属性适合放常量或跨实例计数;别把可变对象当类属性默认值(见常见错误)。

三种方法

class User:
    role = "member"           # 类属性

    def __init__(self, name: str):
        self.name = name

    def greet(self):          # 实例方法
        return f"我是 {self.name}"

    @classmethod
    def admin(cls, name: str) -> "User":      # 类方法:拿到类本身
        return cls(name)

    @staticmethod
    def is_valid_name(name: str) -> bool:     # 静态方法:不需要实例或类
        return len(name.strip()) > 0
  • 实例方法:操作实例状态。
  • @classmethod:替代构造器(工厂方法),cls 会被正确绑定为子类。
  • @staticmethod:逻辑上属于这个类,但不访问任何状态。

@property:把计算伪装成属性

class User:
    def __init__(self, first: str, last: str):
        self.first = first
        self.last = last

    @property
    def full_name(self) -> str:
        return f"{self.first} {self.last}"

    @full_name.setter
    def full_name(self, value: str) -> None:
        self.first, self.last = value.split(" ", 1)

u = User("张", "三")
u.full_name             # '张 三',像属性一样访问
u.full_name = "李 四"    # 走 setter

好处:以后改成存储字段(或加校验)时,调用方代码不用改。

继承

class Animal:
    def __init__(self, name: str):
        self.name = name

    def speak(self) -> str:
        raise NotImplementedError

class Dog(Animal):
    def speak(self) -> str:      # 重写父类方法
        return "汪"

    def fetch(self) -> str:
        return f"{self.name} 去捡球"

class Dog(Animal):
    def __init__(self, name: str, breed: str):
        super().__init__(name)    # 调用父类初始化
        self.breed = breed

super() 在多重继承里按 MRO(方法解析顺序)找下一个类,不要把父类名写死。查看顺序:

Dog.__mro__

多重继承要慎用,容易变成菱形问题;优先用组合:

class Order:
    def __init__(self, user: User, items: list[str]):   # 持有对象,而不是继承它
        ...

常用魔术方法

class Point:
    def __init__(self, x: float, y: float):
        self.x, self.y = x, y

    def __repr__(self) -> str:          # 调试输出,给开发者看
        return f"Point({self.x}, {self.y})"

    def __str__(self) -> str:           # str() / print,给用户看
        return f"({self.x}, {self.y})"

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Point):
            return NotImplemented
        return (self.x, self.y) == (other.x, other.y)

    def __hash__(self) -> int:          # 定义 __eq__ 后默认不可哈希
        return hash((self.x, self.y))

    def __len__(self) -> int: ...       # len(obj)
    def __iter__(self): ...             # for x in obj
    def __getitem__(self, key): ...     # obj[key]
    def __enter__ / __exit__: ...       # with 语句

至少要写 __repr__:调试和日志全靠它。

__slots__ 可以省内存并禁止动态加属性:

class Point:
    __slots__ = ("x", "y")

常见错误与注意事项

1. 可变对象当类属性

class Bag:
    items = []            # 危险:所有实例共享同一个列表

b1, b2 = Bag(), Bag()
b1.items.append("x")
b2.items                  # ['x'],被污染了

放进 __init__ 里创建。

2. 方法忘了写 self

调用时报 TypeError: greet() takes 0 positional arguments but 1 was given。

3. __eq__ 没处理其他类型

直接访问 other.x 会在比较不同类时抛 AttributeError。用 isinstance 检查,不匹配返回 NotImplemented。

4. 定义了 __eq__ 却忘了 __hash__

对象会变成不可哈希,放进 set 或当字典键时报错。

5. 过度使用继承

只为复用一两个方法就继承,会让类层次变脆。优先组合与模块级函数。

小结

  • 类把数据和行为绑在一起;__init__ 初始化,self 是实例。
  • @property 让计算属性与存储属性可以互换;@classmethod 做工厂方法。
  • 继承用 super(),多重继承谨慎,优先组合。
  • 至少实现 __repr__;__eq__ 与 __hash__ 成对定义。

下一步