偏函数
偏函数(partial function)通过预先固定一个函数的部分参数,得到一个参数更少的新函数。Python 标准库 functools.partial 可以完成这件事,不需要重复编写包装函数。
例如,已有 int(text, base),希望得到一个始终按二进制解析文本的函数,就可以固定 base=2。偏函数适合配置回调、适配接口和减少重复参数。
本文使用 Python 3.10 及以上版本。读完后,你应该能够使用 partial() 固定位置参数或关键字参数,理解参数覆盖规则,并区分它与默认参数、lambda 和 partialmethod 的用途。
基本用法
from functools import partial
parse_binary = partial(int, base=2)
print(parse_binary("1010")) # 10
print(parse_binary("1111")) # 15
partial(int, base=2) 返回一个新的可调用对象。调用 parse_binary("1010") 时,实际执行的是 int("1010", base=2)。
也可以预先固定位置参数:
from functools import partial
def power(base, exponent):
return base**exponent
square = partial(power, exponent=2)
cube = partial(power, exponent=3)
print(square(4)) # 16
print(cube(4)) # 64
固定第一个位置参数时,后续调用参数会接在已固定参数之后:
def add_three(first, second, third):
return first + second + third
add_to_ten = partial(add_three, 10)
print(add_to_ten(2, 3)) # 15
参数覆盖与检查
偏函数保存了原函数、位置参数和关键字参数,可以通过只读属性查看:
from functools import partial
def greet(name, punctuation="!"):
return f"你好,{name}{punctuation}"
friendly_greet = partial(greet, punctuation="!")
print(friendly_greet.func is greet) # True
print(friendly_greet.args) # ()
print(friendly_greet.keywords) # {'punctuation': '!'}
调用时传入同名关键字,可以覆盖偏函数保存的关键字:
print(friendly_greet("小林", punctuation="。")) # 你好,小林。
位置参数不能被“跳过”后再固定后面的参数。需要固定后面的参数时,优先使用关键字参数;如果参数没有名称或规则很复杂,可以改写成 lambda 或普通函数。
用于回调和高阶函数
很多 API 的回调只允许接收一个参数,而现有函数需要更多配置。partial 可以把配置提前绑定:
from functools import partial
def convert_price(price, rate, digits):
return round(price * rate, digits)
to_usd = partial(convert_price, rate=0.14, digits=2)
prices = [100, 250, 80]
converted = list(map(to_usd, prices))
print(converted) # [14.0, 35.0, 11.2]
这里 map() 每次只传入 price,rate 和 digits 已经由偏函数提供。与 lambda price: convert_price(price, 0.14, 2) 相比,partial 更明确地表达“固定已有函数的参数”。
与默认参数和 lambda 的比较
默认参数
如果你能修改函数定义,并且固定值属于函数本身的自然默认行为,直接使用默认参数最简单:
def connect(host, timeout=5):
...
如果函数来自第三方库或需要在不同调用点固定不同配置,就不能修改原函数,此时可使用 partial。
lambda 或包装函数
三种写法可能产生相同效果:
from functools import partial
def multiply(left, right):
return left * right
double_by_partial = partial(multiply, right=2)
double_by_lambda = lambda value: multiply(value, right=2)
def double_by_def(value):
return multiply(value, right=2)
可以按下面的原则选择:
| 场景 | 推荐写法 |
|---|---|
| 能修改定义且默认值自然 | 函数默认参数 |
| 只固定已有函数的参数 | functools.partial |
| 需要重新排列参数、分支或额外处理 | lambda 或 def |
| 需要复用、调试和文档 | def |
partial 只负责绑定参数,不会自动改变原函数的业务逻辑,也不会把参数顺序重新排列。
partialmethod:用于类方法
在类中,如果想基于另一个方法预先固定参数,应使用 functools.partialmethod:
from functools import partialmethod
class Request:
def send(self, method, path, *, timeout=5):
return f"{method} {path}, timeout={timeout}"
get = partialmethod(send, "GET")
post = partialmethod(send, "POST")
request = Request()
print(request.get("/users")) # GET /users, timeout=5
print(request.post("/login")) # POST /login, timeout=5
partialmethod 会在实例调用时正确处理 self;直接把 partial 放在类属性上通常不能得到相同的绑定行为。类方法、静态方法和描述符组合复杂时,优先用显式方法保持可读性。
可调用对象与类型提示
偏函数可以像普通函数一样传递给接收可调用对象的 API。类型提示可以使用 Callable 描述它的外部签名:
from collections.abc import Callable
from functools import partial
def make_parser(base: int) -> Callable[[str], int]:
return partial(int, base=base)
parse_hex = make_parser(16)
print(parse_hex("ff")) # 255
需要注意,静态类型检查器不一定能从所有复杂的 partial 调用中精确推导参数类型。公共接口可以通过 Callable 或命名包装函数补充清晰的类型信息。
常见错误与边界
固定位置错误
def format_value(prefix, value, suffix):
return f"{prefix}{value}{suffix}"
with_brackets = partial(format_value, "[", suffix="]")
print(with_brackets("内容")) # [内容]
如果直接写 partial(format_value, "[", "]"),两个值会依次绑定 prefix 和 value,调用时就无法再传入真正的 value。固定后面的参数时使用关键字更安全。
把 partial 当作结果
parse_hex = partial(int, base=16)
print(parse_hex("10")) # 16
partial(...) 本身只是创建函数,不会执行原函数;必须再调用返回的偏函数。与 int("10", 16) 的区别正是“创建可复用规则”和“立即得到结果”。
过度绑定和隐藏依赖
一次固定太多参数会让新函数的用途不明显;关键配置应通过命名变量或工厂函数表达。偏函数也不会复制可变参数对象,若固定的是列表或字典,后续修改该对象会影响调用结果。
练习
- 使用
partial创建一个始终以 10 为底的整数解析函数。 - 将一个接收多个参数的函数适配到
map()的单参数回调接口。 - 使用
partialmethod为类添加get和post两个快捷方法。 - 比较默认参数、
partial和lambda在调试信息与可读性上的差异。
小结
functools.partial通过预先绑定参数创建新的可调用对象。- 位置参数按顺序追加,固定后面的参数时通常使用关键字;调用时可覆盖已保存的关键字。
partial很适合把已有函数适配为回调,逻辑复杂或需要重排参数时应使用lambda或def。- 类方法应使用
partialmethod,它会正确处理实例的self。 - 偏函数只绑定参数,不改变原函数逻辑;固定可变对象时要留意共享状态。