算法工程师基本功:动态给 Python 对象绑定方法
一、函数 vs 方法
先区分两个基础概念:
| 概念 | 说明 |
|---|---|
| 函数(function) | 封装的可复用代码块 |
| 方法(method) | 绑定了实例的函数,调用时自动将实例作为第一个参数(通常命名为 self)传入 |
Python 允许动态给对象添加属性,即使这个属性的值是一个函数。但直接赋值得到的只是函数类型的属性,不会自动传递 self,不属于 OOP 意义上的绑定方法。
二、四种绑定方式
方式一:直接赋值(非绑定,不推荐)
python
class Person:
def __init__(self, name):
self.name = name
def greet(obj):
return f'Hello, {obj.name}'
alice = Person('Alice')
alice.greet = greet
print(alice.greet(alice)) # ✅ 必须手动传 self,否则报错
缺点:调用时必须显式传入实例,不符合 OOP 的调用习惯。
方式二:types.MethodType(推荐)
python
import types
alice.greet = types.MethodType(greet, alice)
print(alice.greet()) # ✅ 自动传递 self,无需传参
MethodType 将函数和实例绑定,生成一个真正的 method 对象,调用时自动注入 self。
方式三:functools.partial(偏函数)
python
from functools import partial
alice.greet = partial(greet, obj=alice)
print(alice.greet()) # ✅ 效果和 MethodType 一样
偏函数通过预填充参数来模拟绑定,调用时同样不用传参。
方式四:闭包
python
def bind_method(fun, obj):
def wrapper():
return fun(obj)
return wrapper
alice.greet = bind_method(greet, alice)
print(alice.greet()) # ✅ 效果同上
闭包是最原始的实现方式,本质上和偏函数思路相同------用外层作用域记住实例。
三、MethodType vs partial 对比
这两种方式看起来效果完全一样,但在底层有本质区别:
| 对比维度 | MethodType |
functools.partial |
|---|---|---|
| 返回类型 | <class 'method'>(真正的 OOP 方法) |
<class 'functools.partial'>(可调用对象,仍是函数) |
| 可固定的参数 | 只能固定第一个参数(self) |
可固定任意个位置参数和关键字参数 |
| 存储实例的方式 | bound_method.__self__ |
partial_obj.keywords 或 partial_obj.args |
isinstance 判断 |
是 types.MethodType |
不是 |
| 序列化(pickle) | 有限支持 | 有限支持 |
验证代码:
python
from functools import partial
from types import MethodType
class Person:
def __init__(self, name):
self.name = name
def greet(obj):
return f'Hello, {obj.name}'
alice = Person('Alice')
alice.greet_partial = partial(greet, obj=alice)
alice.greet_methodtype = MethodType(greet, alice)
print(type(alice.greet_partial)) # <class 'functools.partial'>
print(type(alice.greet_methodtype)) # <class 'method'>
print(alice.greet_partial.keywords) # {'obj': <Person object>}
print(alice.greet_methodtype.__self__) # <Person object>
四、实际场景:给 ORM 模型动态添加方法
在机器学习工程中,这种技巧常用于给 ORM 模型或配置类动态注入查询方法:
python
from functools import partial
class Stock:
def __init__(self, code):
self.code = code
def query_history(stock, start_date, end_date):
return f"查询 {stock.code} 在 {start_date} 到 {end_date} 的历史行情"
# 动态绑定带多个参数的偏函数
stock = Stock('000001.SZ')
stock.query = partial(query_history, stock, start_date='2026-01-01', end_date='2026-06-30')
print(stock.query()) # 查询 000001.SZ 在 2026-01-01 到 2026-06-30 的历史行情
这就是 partial 相比 MethodType 的优势------可以绑定任意多个参数 ,不局限于 self。
五、总结
| 绑定方式 | 自动传递 self | 灵活度 | 推荐场景 |
|---|---|---|---|
| 直接赋值 | ❌ | 低 | 不推荐 |
MethodType |
✅ | 只能固定 self | 标准的 OOP 绑定 |
partial |
✅ | 可固定任意参数 | 需要同时绑定多个参数时 |
| 闭包 | ✅ | 最高,可做任意转换 | 理解原理,面试手撕 |
实际开发中,推荐优先使用 MethodType 做标准的实例方法绑定;当需要同时固定其他参数时,改用 partial。