前置知识: 零基础起步

面向对象编程

8 min中级

类与对象、继承、多态、魔术方法与描述符。

知识点地图

  • 知识类别:Python 面向对象编程的基础机制——类与实例、属性与方法(实例/类/静态)、继承与多态、魔术方法(运算符重载与对象行为定制)。
  • 解决什么问题:把「数据 + 操作」聚合成类型:同一份购物车逻辑服务网页与脚本两处调用时不想复制两遍(封装成类);不同形状都要算面积但公式不同(继承 + 多态);自定义对象要能进排序、能比较、能打印(魔术方法)。
  • 什么时候用到:任何业务实体建模(用户、订单、配置);给一组类建立共同接口(配合 抽象基类与协议);让自定义类型融入语言习惯(len()、for、== 的背后全是魔术方法)。类设计的取舍(封装约定、组合优于继承)见 面向对象进阶;数据承载类优先用 dataclass(见 数据类与 Pydantic)。

前置知识

学习目标

  • 掌握「1. 类与实例 (Class & Instance)」的核心机制、典型用法与常见陷阱
  • 掌握「2. 属性与方法 (Properties & Methods)」的核心机制、典型用法与常见陷阱
  • 掌握「3. 继承与多态 (Inheritance & Polymorphism)」的核心机制、典型用法与常见陷阱
  • 掌握「4. 特殊方法 (Magic Methods)」的核心机制、典型用法与常见陷阱
  • 掌握「5. 高级面向对象特性」的核心机制、典型用法与常见陷阱

1. 类与实例 (Class & Instance)

Python 是一门纯粹的面向对象语言,几乎所有的东西都是对象。

1.1 类的定义

python
 class Person:
  # 类属性 | Class attribute
  species = "Human"
  # 构造方法 | Constructor
  def __init__(self, name, age):
  self.name = name # 实例属性 | Instance attribute
  self.age = age
  # 实例方法 | Instance method
  def greet(self):
  return f"Hello, I am {self.name}"
 # 实例化
 p = Person("Alice", 30)
 print(p.name) # 输出: Alice
 print(p.age) # 输出: 30
 print(p.greet()) # 输出: Hello, I am Alice
 # 访问类属性
 print(Person.species) # 输出: Human
 print(p.species) # 输出: Human(实例也可以访问类属性)
 # 修改类属性
 Person.species = "Homo sapiens"
 print(Person.species) # 输出: Homo sapiens
 print(p.species) # 输出: Homo sapiens(实例的类属性也会改变)
 # 修改实例的类属性(只会影响该实例)
 p.species = "Human"
 print(Person.species) # 输出: Homo sapiens
 print(p.species) # 输出: Human

1.2 类的文档字符串

python
 class Person:
  """人类类"""
  def __init__(self, name, age):
  """初始化方法"""
  self.name = name
  self.age = age
  def greet(self):
  """打招呼方法"""
  return f"Hello, I am {self.name}"
 # 访问文档字符串
 print(Person.__doc__) # 输出: 人类类
 print(Person.__init__.__doc__) # 输出: 初始化方法
 print(Person.greet.__doc__) # 输出: 打招呼方法

2. 属性与方法 (Properties & Methods)

2.1 实例方法

实例方法是最常见的方法类型,它接收 self 作为第一个参数,指向当前实例。

python
 class Circle:
  def __init__(self, radius):
  self.radius = radius
  def area(self):
  """计算圆的面积"""
  return 3.14159 * self.radius ** 2
  def circumference(self):
  """计算圆的周长"""
  return 2 * 3.14159 * self.radius
 # 使用实例方法
 c = Circle(5)
 print(c.area()) # 输出: 78.53975
 print(c.circumference()) # 输出: 31.4159

2.2 类方法

类方法使用 @classmethod 装饰器,接收 cls 作为第一个参数,指向类本身。

python
 class Person:
  species = "Human"
  def __init__(self, name, age):
  self.name = name
  self.age = age
  @classmethod
  def get_species(cls):
  """获取物种信息"""
  return cls.species
  @classmethod
  def create_adult(cls, name):
  """创建一个成年人实例"""
  return cls(name, 18)
 # 使用类方法
 print(Person.get_species()) # 输出: Human
 # 使用类方法创建实例
 adult = Person.create_adult("Bob")
 print(adult.name) # 输出: Bob
 print(adult.age) # 输出: 18

2.3 静态方法

静态方法使用 @staticmethod 装饰器,不接收 self 或 cls 参数,是类的纯工具函数。

python
 class Math:
  @staticmethod
  def add(a, b):
  """加法运算"""
  return a + b
  @staticmethod
  def multiply(a, b):
  """乘法运算"""
  return a * b
 # 使用静态方法
 print(Math.add(2, 3)) # 输出: 5
 print(Math.multiply(2, 3)) # 输出: 6
 # 也可以通过实例调用静态方法
 m = Math()
 print(m.add(4, 5)) # 输出: 9

2.4 私有属性和方法

在 Python 中,使用双下划线 __ 开头的属性和方法被视为私有的,外部无法直接访问。

python
 class Person:
  def __init__(self, name, age):
  self.name = name # 公开属性
  self.__age = age # 私有属性
  def __private_method(self):
  """私有方法"""
  return f"This is a private method"
  def get_age(self):
  """获取年龄(通过公开方法访问私有属性)"""
  return self.__age
  def set_age(self, age):
  """设置年龄(通过公开方法修改私有属性)"""
  if age > 0:
  self.__age = age
 # 使用
 p = Person("Alice", 30)
 print(p.name) # 输出: Alice
 # print(p.__age) # 错误: 'Person' object has no attribute '__age'
 # print(p.__private_method()) # 错误: 'Person' object has no attribute '__private_method'
 # 通过公开方法访问私有属性
 print(p.get_age()) # 输出: 30
 p.set_age(31)
 print(p.get_age()) # 输出: 31
 # 注意: Python 实际上是通过名称修饰来实现私有性的
 # 可以通过 _ClassName__private 来访问私有属性(不推荐)
 print(p._Person__age) # 输出: 31
 print(p._Person__private_method()) # 输出: This is a private method

2.5 装饰器属性 (@property)

@property 装饰器可以将方法伪装成属性,使得可以像访问属性一样访问方法。

python
 class Person:
  def __init__(self, first_name, last_name):
  self.first_name = first_name
  self.last_name = last_name
  @property
  def full_name(self):
  """获取全名"""
  return f"{self.first_name} {self.last_name}"
  @full_name.setter
  def full_name(self, name):
  """设置全名"""
  first_name, last_name = name.split()
  self.first_name = first_name
  self.last_name = last_name
  @full_name.deleter
  def full_name(self):
  """删除全名"""
  self.first_name = None
  self.last_name = None
 # 使用属性装饰器
 p = Person("Alice", "Smith")
 print(p.full_name) # 输出: Alice Smith
 # 设置属性
 p.full_name = "Bob Johnson"
 print(p.first_name) # 输出: Bob
 print(p.last_name) # 输出: Johnson
 print(p.full_name) # 输出: Bob Johnson
 # 删除属性
 del p.full_name
 print(p.first_name) # 输出: None
 print(p.last_name) # 输出: None
 # print(p.full_name) # 输出: None None

3. 继承与多态 (Inheritance & Polymorphism)

3.1 单继承

python
 class Animal:
  def __init__(self, name):
  self.name = name
  def speak(self):
  return "Some generic sound"
 class Dog(Animal):
  def speak(self):
  return "Woof!"
 class Cat(Animal):
  def speak(self):
  return "Meow!"
 # 使用
 animal = Animal("Generic Animal")
 dog = Dog("Rex")
 cat = Cat("Whiskers")
 print(animal.speak()) # 输出: Some generic sound
 print(dog.speak()) # 输出: Woof!
 print(cat.speak()) # 输出: Meow!
 # 检查继承关系
 print(isinstance(dog, Animal)) # 输出:
 print(issubclass(Dog, Animal)) # 输出:

3.2 多继承

Python 支持多继承,一个子类可以继承多个父类。

python
 class A:
  def method(self):
  return "A.method()"
 class B:
  def method(self):
  return "B.method()"
  def other_method(self):
  return "B.other_method()"
 class C(A, B):
  pass
 # 使用
 c = C()
 print(c.method()) # 输出: A.method()(按照 MRO 顺序,先 A 后 B)
 print(c.other_method()) # 输出: B.other_method()
 # 查看方法解析顺序 (MRO)
 print(C.__mro__) # 输出: (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)

3.3 super() 函数

super() 函数用于调用父类的方法,在继承中非常有用。

python
 class Parent:
  def __init__(self, name):
  self.name = name
  def greet(self):
  return f"Hello from {self.name}"
 class Child(Parent):
  def __init__(self, name, age):
  super().__init__(name) # 调用父类的 __init__ 方法
  self.age = age
  def greet(self):
  parent_greeting = super().greet() # 调用父类的 greet 方法
  return f"{parent_greeting}, I'm {self.age} years old"
 # 使用
 child = Child("Bob", 10)
 print(child.greet()) # 输出: Hello from Bob, I'm 10 years old

3.4 多态

多态是指不同类型的对象可以使用相同的接口。

python
 def make_speak(animal):
  """让动物发声"""
  print(animal.speak())
 # 使用
 make_speak(Dog("Rex")) # 输出: Woof!
 make_speak(Cat("Whiskers")) # 输出: Meow!
 make_speak(Animal("Generic")) # 输出: Some generic sound

3.5 鸭子类型

Python 采用鸭子类型,不关注对象的具体类型,只关注对象是否具有所需的方法或属性。

python
 class Duck:
  def quack(self):
  return "Quack!"
 class Person:
  def quack(self):
  return "I'm quacking like a duck!"
 def make_quack(obj):
  """让对象发出鸭叫声"""
  if hasattr(obj, 'quack'):
  print(obj.quack())
  else:
  print("This object can't quack")
 # 使用
 make_quack(Duck()) # 输出: Quack!
 make_quack(Person()) # 输出: I'm quacking like a duck!

4. 特殊方法 (Magic Methods)

特殊方法(也称为魔术方法或双下划线方法)是 Python 中具有特殊功能的方法,它们以双下划线开头和结尾。

4.1 基本特殊方法

python
 class Person:
  def __init__(self, name, age):
  """初始化方法"""
  self.name = name
  self.age = age
  def __str__(self):
  """字符串表示(对用户友好)"""
  return f"Person(name='{self.name}', age={self.age})"
  def __repr__(self):
  """字符串表示(对开发者友好)"""
  return f"Person('{self.name}', {self.age})"
  def __eq__(self, other):
  """相等性比较"""
  if not isinstance(other, Person):
  return NotImplemented
  return self.name == other.name and self.age == other.age
  def __lt__(self, other):
  """小于比较"""
  if not isinstance(other, Person):
  return NotImplemented
  return self.age < other.age
 # 使用
 p1 = Person("Alice", 30)
 p2 = Person("Bob", 25)
 p3 = Person("Alice", 30)
 print(str(p1)) # 输出: Person(name='Alice', age=30)
 print(repr(p1)) # 输出: Person('Alice', 30)
 print(p1 == p2) # 输出: False
 print(p1 == p3) # 输出:
 print(p1 < p2) # 输出: False
 print(p2 < p1) # 输出:

4.2 容器相关的特殊方法

python
 class MyList:
  def __init__(self, items):
  self.items = items
  def __len__(self):
  """获取长度"""
  return len(self.items)
  def __getitem__(self, index):
  """获取元素"""
  return self.items[index]
  def __setitem__(self, index, value):
  """设置元素"""
  self.items[index] = value
  def __contains__(self, item):
  """检查元素是否存在"""
  return item in self.items
 # 使用
 my_list = MyList([1, 2, 3, 4, 5])
 print(len(my_list)) # 输出: 5
 print(my_list[0]) # 输出: 1
 my_list[0] = 10
 print(my_list[0]) # 输出: 10
 print(3 in my_list) # 输出:
 print(100 in my_list) # 输出: False
 # 支持迭代
 for item in my_list:
  print(item, end=" ") # 输出: 10 2 3 4 5

4.3 算术运算符特殊方法

python
 class Vector:
  def __init__(self, x, y):
  self.x = x
  self.y = y
  def __add__(self, other):
  """加法运算"""
  if not isinstance(other, Vector):
  return NotImplemented
  return Vector(self.x + other.x, self.y + other.y)
  def __sub__(self, other):
  """减法运算"""
  if not isinstance(other, Vector):
  return NotImplemented
  return Vector(self.x - other.x, self.y - other.y)
  def __mul__(self, scalar):
  """乘法运算"""
  return Vector(self.x * scalar, self.y * scalar)
  def __str__(self):
  return f"Vector({self.x}, {self.y})"
 # 使用
 v1 = Vector(1, 2)
 v2 = Vector(3, 4)
 print(v1 + v2) # 输出: Vector(4, 6)
 print(v1 - v2) # 输出: Vector(-2, -2)
 print(v1 * 2) # 输出: Vector(2, 4)

5. 高级面向对象特性

5.1 抽象基类

抽象基类(Abstract Base Class, ABC)用于定义接口,强制子类实现特定的方法。

python
 from abc import ABC, abstractmethod
 class Animal(ABC):
  @abstractmethod
  def speak(self):
  """动物发声"""
  pass
  @abstractmethod
  def move(self):
  """动物移动"""
  pass
 class Dog(Animal):
  def speak(self):
  return "Woof!"
  def move(self):
  return "Running"
 class Cat(Animal):
  def speak(self):
  return "Meow!"
  def move(self):
  return "Walking"
 # 使用
 dog = Dog()
 print(dog.speak()) # 输出: Woof!
 print(dog.move()) # 输出: Running
 cat = Cat()
 print(cat.speak()) # 输出: Meow!
 print(cat.move()) # 输出: Walking
 # 不能实例化抽象基类
 # animal = Animal() # 错误: Can't instantiate abstract class Animal with abstract methods move, speak

5.2 混入类 (Mixins)

混入类是一种特殊的类,用于为其他类提供功能,而不是作为独立的类使用。

python
 class SwimMixin:
  def swim(self):
  return "Swimming"
 class FlyMixin:
  def fly(self):
  return "Flying"
 class Duck(SwimMixin, FlyMixin):
  def quack(self):
  return "Quack!"
 class Penguin(SwimMixin):
  def walk(self):
  return "Walking"
 # 使用
 duck = Duck()
 print(duck.quack()) # 输出: Quack!
 print(duck.swim()) # 输出: Swimming
 print(duck.fly()) # 输出: Flying
 penguin = Penguin()
 print(penguin.swim()) # 输出: Swimming
 print(penguin.walk()) # 输出: Walking
 # print(penguin.fly()) # 错误: 'Penguin' object has no attribute 'fly'

5.3 单例模式

单例模式确保一个类只有一个实例。

python
 class Singleton:
  _instance = None
  def __new__(cls, *args, **kwargs):
  if not cls._instance:
  cls._instance = super().__new__(cls)
  return cls._instance
  def __init__(self, value=None):
  if value is not None:
  self.value = value
 # 使用
 s1 = Singleton("First")
 print(s1.value) # 输出: First
 # 第二次创建会返回同一个实例
 s2 = Singleton("Second")
 print(s2.value) # 输出: Second
 print(s1 is s2) # 输出:
 print(s1.value) # 输出: Second(因为 s1 和 s2 是同一个实例)

5.4 类装饰器

类装饰器用于修改类的行为。

python
 def debug(cls):
  """类装饰器:为类的所有方法添加调试信息"""
  for name, method in list(cls.__dict__.items()):
  if callable(method) and not name.startswith('__'):
  def wrapper(*args, **kwargs):
  print(f"Calling {name} with args: {args}, kwargs: {kwargs}")
  result = method(*args, **kwargs)
  print(f"{name} returned: {result}")
  return result
  setattr(cls, name, wrapper)
  return cls
 @debug
 class Calculator:
  def add(self, a, b):
  return a + b
  def multiply(self, a, b):
  return a * b
 # 使用
 calc = Calculator()
 calc.add(2, 3) # 输出: Calling add with args: (<__main__.Calculator object at 0x...>, 2, 3), kwargs: {}
  # add returned: 5
 calc.multiply(4, 5) # 输出: Calling multiply with args: (<__main__.Calculator object at 0x...>, 4, 5), kwargs: {}
  # multiply returned: 20

6. 面向对象编程的最佳实践

6.1 设计原则

  • 单一职责原则: 一个类应该只有一个引起它变化的原因
  • 开放-封闭原则: 对扩展开放,对修改封闭
  • 里氏替换原则: 子类应该能够替换父类
  • 依赖倒置原则: 依赖于抽象,而不是具体实现
  • 接口隔离原则: 客户端不应该依赖它不需要的接口

6.2 代码风格

  • 命名约定: 类名使用驼峰命名法(如 MyClass),方法和属性使用蛇形命名法(如 my_method)
  • 文档: 为类、方法和属性添加文档字符串
  • 缩进: 使用 4 个空格进行缩进
  • 行长: 每行代码不超过 79 个字符
  • 导入: 按标准库、第三方库、本地模块的顺序导入

6.3 常见陷阱

  • 可变默认参数: 避免使用可变对象作为默认参数
  • 循环引用: 注意避免循环引用导致的内存泄漏
  • 过度继承: 避免深层继承层次结构
  • 过度使用继承: 考虑使用组合而不是继承
  • 魔法方法滥用: 只在必要时使用魔法方法

7. 实际应用示例

7.1 学生管理系统

python
 class Student:
  def __init__(self, id, name, age, grade):
  self.id = id
  self.name = name
  self.age = age
  self.grade = grade
  def __str__(self):
  return f"Student(id={self.id}, name='{self.name}', age={self.age}, grade={self.grade})"
 class StudentManager:
  def __init__(self):
  self.students = []
  def add_student(self, student):
  """添加学生"""
  self.students.append(student)
  def remove_student(self, student_id):
  """移除学生"""
  self.students = [s for s in self.students if s.id != student_id]
  def get_student(self, student_id):
  """获取学生"""
  for student in self.students:
  if student.id == student_id:
  return student
  return None
  def get_all_students(self):
  """获取所有学生"""
  return self.students
 # 使用
 manager = StudentManager()
 # 添加学生
 manager.add_student(Student(1, "Alice", 18, 90))
 manager.add_student(Student(2, "Bob", 17, 85))
 manager.add_student(Student(3, "Charlie", 19, 95))
 # 获取所有学生
 for student in manager.get_all_students():
  print(student)
 # 获取特定学生
 student = manager.get_student(2)
 print(f"Found student: {student}")
 # 移除学生
 manager.remove_student(2)
 print("After removing student 2:")
 for student in manager.get_all_students():
  print(student)

7.2 形状计算

python
 from abc import ABC, abstractmethod
 class Shape(ABC):
  @abstractmethod
  def area(self):
  """计算面积"""
  pass
  @abstractmethod
  def perimeter(self):
  """计算周长"""
  pass
 class Rectangle(Shape):
  def __init__(self, width, height):
  self.width = width
  self.height = height
  def area(self):
  return self.width * self.height
  def perimeter(self):
  return 2 * (self.width + self.height)
 class Circle(Shape):
  def __init__(self, radius):
  self.radius = radius
  def area(self):
  return 3.14159 * self.radius ** 2
  def perimeter(self):
  return 2 * 3.14159 * self.radius
 class Triangle(Shape):
  def __init__(self, a, b, c):
  self.a = a
  self.b = b
  self.c = c
  def area(self):
  # 使用海伦公式计算面积
  s = (self.a + self.b + self.c) / 2
  return (s * (s - self.a) * (s - self.b) * (s - self.c)) ** 0.5
  def perimeter(self):
  return self.a + self.b + self.c
 # 使用
 shapes = [
  Rectangle(5, 4),
  Circle(3),
  Triangle(3, 4, 5)
 ]
 for shape in shapes:
  print(f"{type(shape).__name__}: Area = {shape.area():.2f}, Perimeter = {shape.perimeter():.2f}")

动手实践

练习一(预测题):不运行代码,判断输出:

python
class Counter:
    count = 0                      # 类属性

    def __init__(self):
        Counter.count += 1
        self.serial = Counter.count

a = Counter()
b = Counter()
c = Counter()
print(Counter.count, a.serial, b.serial)
b.count = 99                        # 这一行做了什么?
print(Counter.count, b.count)

提示:b.count = 99 是读类属性还是写实例属性?

参考实现

输出:3 1 2 与 3 99。构造三次把类属性 Counter.count 累加到 3,三个实例的 serial 分别是 1、2、3。b.count = 99 是在 b 实例上新建同名实例属性(赋值永远写实例命名空间,不碰类属性)——类属性 Counter.count 仍是 3,b.count 读到的是实例的 99,a.count 读到的仍是类属性的 3。这演示了属性查找的顺序(实例 __dict__ → 类 → 父类)与「读会沿链爬、写只落实例」的不对称——类属性适合全类共享的常量与计数器,可变类属性(列表/字典)共享给所有实例是经典事故。

练习二(实战题):实现 Temperature 类:内部以开尔文存储,对外暴露 celsius property(读写,赋值低于 -273.15 时抛 ValueError);再实现 __repr__ 与 __eq__(温度相同即相等),让它能进 sorted()(提示:排序需要 __lt__,或用 functools.total_ordering 一行补齐全套比较)。

提示:property 的 getter/setter 见本篇属性与方法一节;total_ordering 只需实现 __eq__ 与一个方向比较。

参考实现
python
import functools

@functools.total_ordering
class Temperature:
    def __init__(self, celsius: float):
        self.celsius = celsius            # 走 setter 校验

    @property
    def celsius(self) -> float:
        return self._kelvin - 273.15

    @celsius.setter
    def celsius(self, value: float) -> None:
        if value < -273.15:
            raise ValueError("温度低于绝对零度")
        self._kelvin = value + 273.15

    def __repr__(self) -> str:
        return f"Temperature({self.celsius:.1f}C)"

    def __eq__(self, other) -> bool:
        return isinstance(other, Temperature) and self._kelvin == other._kelvin

    def __lt__(self, other) -> bool:
        return self._kelvin < other._kelvin

temps = [Temperature(30), Temperature(-5), Temperature(20)]
print(sorted(temps))
# [Temperature(-5.0C), Temperature(20.0C), Temperature(30.0C)]
try:
    Temperature(-300)
except ValueError as e:
    print(e)                              # 温度低于绝对零度

要点:__init__ 里 self.celsius = ... 走的是 setter——校验收口在一处,构造路径也逃不掉;total_ordering 用 __eq__ + __lt__ 推导出其余四种比较,省下四份样板;排序、相等、打印三种「融入语言习惯」分别由 __lt__/__eq__/__repr__ 提供,这正是魔术方法的设计动机。

练习三(实战题):写一个 PluginBase 体系:PluginBase 定义 name 属性与抽象方法 run;每个插件子类用类装饰器 @register 自动登记;主程序遍历注册表执行全部插件。对照 抽象基类与协议 的 __init_subclass__ 写法,说出两种注册时机的一个差异。

提示:类装饰器收类、改、返回;注册表是模块级列表。

参考实现
python
from abc import ABC, abstractmethod

PLUGINS: list["PluginBase"] = []

def register(cls):
    PLUGINS.append(cls())
    return cls

class PluginBase(ABC):
    name: str = "unnamed"

    @abstractmethod
    def run(self) -> None: ...

@register
class HelloPlugin(PluginBase):
    name = "hello"
    def run(self) -> None:
        print("hello from plugin")

@register
class ByePlugin(PluginBase):
    name = "bye"
    def run(self) -> None:
        print("bye from plugin")

for p in PLUGINS:
    p.run()

差异:类装饰器是显式注册(忘了贴 @register 插件就不生效,好在意图可见);__init_subclass__ 是隐式注册(定义即注册,不会忘,但「谁被注册了」要靠读基类才知道)。框架收第三方插件偏好后者(少一步使用成本),团队内部代码偏好前者(意图显式、便于审查)。

练习四(找错题):这个 Rectangle 有两处问题,先找再修:

python
class Rectangle:
    def __init__(self, width, height):
        width = width
        self.height = height

    def area(self):
        return self.width * self.height

r = Rectangle(3, 4)
print(r.area())

提示:width = width 赋给了谁?

参考实现
python
class Rectangle:
    def __init__(self, width: float, height: float):
        self.width = width          # 少了 self. 前缀:局部变量自赋值
        self.height = height

    def area(self) -> float:
        return self.width * self.height

两处问题:其一,width = width 只是把参数赋给局部变量自己,实例上根本没有 width 属性——area() 里 self.width 抛 AttributeError,这是「参数没挂到 self 上」的经典笔误,写 self.width = width;其二(顺带):height 虽然对了,但两行写法不一致本身就是该类笔误的温床,统一 self.x = x 形态并开 mypy 后,这类问题在静态检查就会被指出。

练习五(实战题):实现一个 Stack 类:魔术方法让它支持 len()、in(__contains__)、可迭代(从栈顶到栈底)、+ 合并两个栈(返回新栈);再用 dataclass 重写同一功能对比代码量(dataclass 用法见 数据类与 Pydantic)。

提示:__len__、__contains__、__iter__、__add__;内部用 list 存即可。

参考实现
python
class Stack:
    def __init__(self, items=()):
        self._items = list(items)

    def push(self, item) -> None:
        self._items.append(item)

    def pop(self):
        if not self._items:
            raise IndexError("pop from empty stack")
        return self._items.pop()

    def __len__(self) -> int:
        return len(self._items)

    def __contains__(self, item) -> bool:
        return item in self._items

    def __iter__(self):
        return reversed(self._items)      # 栈顶(末尾)先出

    def __add__(self, other: "Stack") -> "Stack":
        if not isinstance(other, Stack):
            return NotImplemented
        return Stack(self._items + other._items)

s = Stack([1, 2, 3])
print(len(s), 2 in s)                     # 3 True
print(list(iter(s)))                      # [3, 2, 1]
print(list(s + Stack([4])))               # [4, 3, 2, 1]

要点:__iter__ 返回 reversed(...)——列表的反向迭代器,栈顶优先的语义在迭代器里表达;__add__ 对不支持的类型返回 NotImplemented(不是 NotImplementedError)——把「试另一个操作数的反射方法」的机会还给解释器,这是运算符重载的标准协议;pop 空栈抛 IndexError 与 list 行为对齐。

自我检查

  • 能解释实例属性与类属性的查找顺序,并预测「实例赋值同名属性」后的读取结果;
  • 能写出 property 的 getter/setter 并把校验收口在 setter;
  • 能用 functools.total_ordering 或手写 __eq__/__lt__ 让自定义对象可排序;
  • 能说出静态方法与类方法各自的使用现场;
  • 能解释 __init__ 里漏写 self.x = x 的后果与排查方法;
  • 能给自定义类补上 __len__/__iter__/__contains__/__add__ 让它融入内建操作。