人人都会AI编程

28.2 猴子补丁、动态属性、动态方法

更新时间:2026-07-12

Python 的动态特性让程序在运行时也能修改对象的结构和行为,这在很多语言中是做不到的。其中猴子补丁动态属性动态方法是三个典型的用法,它们很灵活,但用不好也容易造成混乱。


猴子补丁(Monkey Patching)

猴子补丁指的是在运行时动态修改类或模块的方法、属性,而无需改动原始源代码。常用于临时修复第三方库的 bug、在不允许修改源码的情况下调整行为,或者在不破坏原有接口的前提下扩展功能。

示例:替换模块函数

import json

# 保存原始函数
_original_dumps = json.dumps

def _patched_dumps(obj, **kwargs):
    # 强制设定 ensure_ascii=False,输出中文而非 Unicode 转义
    kwargs['ensure_ascii'] = False
    return _original_dumps(obj, **kwargs)

# 打补丁
json.dumps = _patched_dumps

# 后续所有调用 json.dumps 都会走补丁逻辑
print(json.dumps({'name': '张三'}))
# 输出: {"name": "张三"}  (而不是 {"name": "\u5f20\u4e09"})

示例:修补类方法

class Target:
    def greet(self):
        return "Hello"

def new_greet(self):
    return "你好"

# 运行时替换方法
Target.greet = new_greet

t = Target()
print(t.greet())  # 输出: 你好

何时使用猴子补丁?

  • 修复第三方库的紧急 bug,在等待官方修复期间作为临时方案。
  • 为遗留代码补充功能,而无法修改原代码。
  • 编写测试时用 mock 替换有副作用的函数。

注意事项:

  • 补丁会影响所有使用该模块的代码,极易引入隐蔽的全局副作用。
  • 强烈建议在打补丁时保存原始引用,并在不需要时恢复,或者使用 unittest.mock.patch 等工具安全地操作。
  • 过度使用会让代码变得难以理解和调试,应该作为最后手段。

动态属性

Python 对象的属性和方法实际上都存储在内部的 dict 字典里,你可以在运行时自由地添加、修改和删除。

直接添加实例属性

class User:
    def __init__(self, name):
        self.name = name

u = User("张三")
u.age = 30        # 运行时动态添加属性
print(u.age)      # 30

实例属性是挂在具体对象上的,不影响类定义。

通过 setattr / getattr 操作属性

setattr(u, 'email', 'zhangsan@example.com')
print(getattr(u, 'email'))  # zhangsan@example.com

当属性名来自变量时,这种方式更方便。

动态创建类属性(影响所有新实例)

User.role = 'admin'    # 直接在类上添加属性
u2 = User("李四")
print(u2.role)          # admin

防误删 / 防动态添加:slots

class Fixed:
    __slots__ = ('name', 'age')  # 只允许这两个属性

f = Fixed()
f.name = '小红'
f.email = 'test@test.com'   # AttributeError

使用 slots 可以阻止动态添加属性,同时节省内存,适合需要严格控制属性且创建大量实例的场景。


动态方法

动态方法是指在运行时给某个实例或类绑定一个新的函数,让它像普通方法一样工作。这比猴子补丁更局部化,通常是给特定实例临时添加行为。

给实例绑定方法

class Dog:
    def __init__(self, name):
        self.name = name

def bark(self):
    return f"{self.name} says Woof!"

d = Dog("旺财")
# 直接赋值不会自动传递 self,需要使用 types.MethodType 包装
import types
d.bark = types.MethodType(bark, d)
print(d.bark())  # 旺财 says Woof!

注意:这样绑定的方法只存在于该实例,其他实例不受影响。

给类绑定方法

Dog.run = lambda self: f"{self.name} is running"
d2 = Dog("来福")
print(d2.run())  # 来福 is running

给类绑定方法会影响所有实例(包括之前创建的),这与猴子补丁完全相同。

使用 getattr / getattribute 动态生成方法

class Router:
    def __getattr__(self, name):
        if name.startswith('handle_'):
            def handler(*args, **kwargs):
                return f"Handled {name} with args={args}"
            return handler
        raise AttributeError(name)

r = Router()
print(r.handle_login('user', 'pass'))  # Handled handle_login with args=('user', 'pass')

这种模式在框架开发中很常见,比如根据 URL 动态分发处理器,不用预先定义所有方法。


实战建议

  • 能用静态定义解决的问题,不要用动态。动态特性虽然强大,但会降低代码可读性和 IDE 智能提示的准确性。
  • 猴子补丁永远先考虑正规替代:fork 并修复第三方库、使用继承 + 重写、使用配置参数等。
  • 动态属性适合:处理不确定结构的数据(如 JSON 反序列化后的对象包装)、框架内部逻辑(如 ORM 的字段描述符)、编写测试和 mock 工具。
  • 动态方法适合:插件化架构、运行时行为扩展、简化重复代码的模式生成。
  • 保持动态修改的范围尽量小和明确,并在代码中留下必要的注释,说明为什么要这么做。

掌握这些技巧,你就能在必要时跳出常规 OOP 的框架,以更灵活的方式组织代码。但请记住:能力越大,责任越大,动态特性用在对的地方是蜜糖,滥用则可能变成整个项目的维护噩梦。