人人都会AI编程

10.4 魔法方法详解

更新时间:2026-07-12

魔法方法是 Python 对象模型的核心,它们以双下划线开头和结尾(如 init),让你能控制对象的底层行为。掌握魔法方法,就能写出更自然、更 Pythonic 的类,让自定义对象像内置类型一样工作。

构造与析构:initnewdel

  • init(self, ...)

实例化后最常用到的初始化方法。当调用 MyClass(...) 时,Python 先创建对象,然后自动调用 init 进行初始化。不要在这里返回任何值(除了 None

  class Person:
      def __init__(self, name, age):
          self.name = name
          self.age = age
  
  • new(cls, ...)

init 之前被调用,负责创建并返回实例对象。通常不需要重写,除非你要控制单例模式、继承不可变类型(如 strint)时。

  class Singleton:
      _instance = None
      def __new__(cls):
          if cls._instance is None:
              cls._instance = super().__new__(cls)
          return cls._instance
  
  • del(self)

对象被垃圾回收前调用,类似于“析构函数”。但不要依赖它做关键资源释放,因为调用时机不确定;对于文件、连接等资源,请使用上下文管理器 (with)。

  class FileWrapper:
      def __init__(self, filename):
          self.file = open(filename, 'w')
      def __del__(self):
          self.file.close()  # 仅作最终保障,不推荐
  

运算符重载:让你的对象支持 +-==

  • 算术运算符add+)、sub-)、mul*)、truediv/)等。
  class Vector:
      def __init__(self, x, y):
          self.x, self.y = x, y
      def __add__(self, other):
          return Vector(self.x + other.x, self.y + other.y)
  

重载后 v1 + v2 就能直接使用。

  • 比较运算符eq==)、lt<)、gt>)等。实现 eq 后,两个不同实例可以按值比较;如果你还想让对象可排序,就补上 lt 等方法。
  • 字符串表示
  • repr(self):返回“官方”字符串表示,应尽可能准确、明确,eval(repr(obj)) 最好能重建对象。
  • str(self):返回“非正式”可读性好的字符串,被 print()str() 调用。

如果只定义 reprstr 默认会回退到它。

  • 其他常用lenlen())、getitem(索引访问 [])、setitemcontainsin 操作)等,可以让你的类表现得像内置容器。

属性访问控制:getattrsetattrgetattribute

  • getattr(self, name)

仅当通过常规途径找不到属性时被调用。适合实现“惰性属性”或兼容老代码。

  class LazyRecord:
      def __init__(self):
          self._data = {}
      def __getattr__(self, name):
          if name in self._data:
              return self._data[name]
          raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
  
  • setattr(self, name, value)

在每次给属性赋值时被调用,哪怕是 self.xxx = ... 也会触发。重写时要小心死循环:内部必须用 super().setattr() 或直接操作 dict 来真正存储。

  class Validated:
      def __setattr__(self, name, value):
          if name == 'age' and value < 0:
              raise ValueError("age cannot be negative")
          super().__setattr__(name, value)
  
  • getattribute(self, name)

在每次访问属性时无条件被调用,包括访问 dict 本身,极易触发无限递归。除非你很明确在做什么,绝大多数情况用 getattr 即可

容器模拟:让类变成列表/字典的亲戚

实现以下方法,你的对象就能配合 len()[]for 循环等操作:

| 方法 | 触发操作 |
|------|----------|
| len | len(obj) |
| getitem | obj[key],迭代需支持 IndexError 结束 |
| setitem | obj[key] = value |
| delitem | del obj[key] |
| contains | item in obj |
| iter | for x in obj:,返回一个迭代器 |

示例:可循环的页码范围

class RangeWrapper:
    def __init__(self, start, end):
        self.start = start
        self.end = end

    def __len__(self):
        return max(0, self.end - self.start)

    def __getitem__(self, index):
        value = self.start + index
        if value >= self.end:
            raise IndexError
        return value

可调用对象:call

实现了 call 方法的实例可以像函数一样被调用。这常用于创建更灵活的函数对象或装饰器。

class Multiplier:
    def __init__(self, factor):
        self.factor = factor
    def __call__(self, x):
        return x * self.factor

double = Multiplier(2)
print(double(5))  # 10

上下文管理:enterexit

让你可以用 with 语句管理资源。

  • enter:进入 with 块时执行,返回值赋给 as 后的变量。
  • exit(exc_type, exc_val, exc_tb):离开 with 块时调用,异常信息作为参数传入,返回 True 可以吞掉异常。

典型用法:自定义文件打开

class ManagedFile:
    def __init__(self, name, mode='r'):
        self.name = name
        self.mode = mode

    def __enter__(self):
        self.file = open(self.name, self.mode)
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.file.close()
        # 不处理异常,返回 False(默认)让异常继续传播

with ManagedFile('data.txt', 'w') as f: 即可安全读写文件,即使内部抛出异常文件也会被关闭。


实际开发提示

  • 不要为了炫技而滥用魔法方法,只在需要让对象表现得更自然地内置类型时才实现。
  • Python 3.7 引入的 dataclasses 能自动帮你生成 initrepreq 等,大幅减少样板代码。若只需存储数据,优先考虑 dataclassesnamedtuple
  • 当需要使用 setattrgetattribute 时,先想清楚是否有更简单的方式(如 @property)。