Python 中 functools.partial 偏函数
Python functools.partial 偏函数完全指南
什么是偏函数
偏函数(Partial Function)是指通过固定原函数的部分参数,生成一个更简单的新函数。在 Python 中,偏函数由标准库 functools 模块的 partial 类提供。
简单说:如果你有一个函数需要很多参数,但大多数场景下某些参数总是相同的值,你就可以“冻结”这些固定值,得到一个只需剩余参数的新函数。
partial 本身是一个可调用的对象,当调用偏函数时,它会将预先填好的参数与原调用参数合并,然后交给原函数执行。
为什么需要偏函数
- 减少重复代码:避免每次调用都传递相同的参数。
- 提高可读性:生成具有明确语义的新函数名,让意图更清晰。
- 适配回调接口:需要将多参数函数传给只接收单参数函数的框架时(如 GUI 事件处理、多线程回调)。
- 创建快捷方式:在不修改原函数定义的情况下,提供默认行为的变体。
基本语法
from functools import partial
# partial(原函数, *固定位置参数, **固定关键字参数)
新函数 = partial(原函数, arg1, arg2, kwarg1=value)
- 返回的新函数在调用时只需提供未被固定的参数。
- 可以同时固定位置参数和关键字参数。
使用示例
示例 1:固定关键字参数
def power(base, exponent):
return base ** exponent
# 固定 exponent=2 创建平方函数
square = partial(power, exponent=2)
print(square(5)) # 25
print(square(10)) # 100
现在 square 只接受一个参数 base,exponent 始终为 2。
示例 2:固定位置参数
def divide(a, b):
return a / b
# 固定第一个参数 a=10
half = partial(divide, 10)
# 调用时只需传入第二个参数 b
print(half(2)) # 5.0
print(half(5)) # 2.0
示例 3:同时固定位置参数和关键字参数
import json
# 固定 indent 和 ensure_ascii,使 JSON 总是以缩进、可读形式输出
pretty_json = partial(json.dumps, indent=4, ensure_ascii=False)
data = {"name": "张三", "age": 25}
print(pretty_json(data))
输出:
{
"name": "张三",
"age": 25
}
参数覆盖与位置占位符
固定参数被后续调用覆盖
调用偏函数时传入的参数会追加在固定参数的后面,或者覆盖同名的关键字参数。
def greet(greeting, name):
return f"{greeting}, {name}!"
hello = partial(greet, "Hello")
print(hello("World")) # Hello, World!
print(hello("Alice")) # Hello, Alice!
# 调用时也可以覆盖 greeting
print(hello("Bob", greeting="Hi")) # Hi, Bob!
如果原函数不允许重复关键字参数,传参覆盖可能导致 TypeError,但 partial 用正确的顺序处理了这个问题 —— 当调用时提供的关键字参数与固定关键字参数冲突时,调用时提供的值优先。
位置占位符
functools.partial 本身不支持占位符(如只固定中间参数而保留两边参数)。但你可以使用其他第三方工具(如 toolz.curry 或 funcy),或者自己用 lambda 实现。
不过有一种替代方案:使用关键字参数来控制需要固定的部分。
# 想固定中间参数,可通过关键字参数实现
def func(a, b, c):
return a, b, c
# 固定 b=2
p = partial(func, b=2)
print(p(1, 3)) # (1, 2, 3)
print(p(10, c=20)) # (10, 2, 20)
partial 与 lambda 的对比
很多场景下 partial 可以用 lambda 替代,但各有优劣。
| 特性 | partial |
lambda |
|---|---|---|
| 可读性 | 意图明确,固定参数一目了然 | 稍复杂,需阅读表达式 |
| 性能 | 稍快(C 实现) | 稍慢(每次调用创建函数对象) |
| 灵活性 | 只能固定参数 | 可以执行任意计算 |
| 调试 | __repr__ 显示原函数和参数 |
无有意义的名字 |
| 序列化 | 可通过 pickle 序列化 | 通常不可序列化 |
推荐:如果只是单纯的参数绑定,优先使用 partial。它显式地表达了“这是一个偏函数”。
高级用法
作为装饰器使用
可以用 partial 来创建装饰器,固定某个参数。
def add_suffix(prefix, text, suffix):
return f"{prefix}{text}{suffix}"
# 固定 prefix 和 suffix
bracket_wrapper = partial(add_suffix, prefix="[", suffix="]")
print(bracket_wrapper("info")) # [info]
在类方法中绑定 self
class Calculator:
def __init__(self, factor):
self.factor = factor
def multiply(self, x):
return x * self.factor
calc = Calculator(10)
# 固定 calc 实例作为 self,得到一个只需要 x 的函数
multiply_by_10 = partial(calc.multiply)
print(multiply_by_10(5)) # 50
这避免了使用 lambda 捕捉 self。
配合 map 等高阶函数
nums = [1, 2, 3, 4, 5]
# 使用 partial 将 pow 固定 exponent=2
squared = list(map(partial(pow, exp=2), nums))
print(squared) # [1, 4, 9, 16, 25]
使用 partialmethod 定义类方法变体
在类中,可以使用 functools.partialmethod 创建方法的偏函数版本,它类似于 partial 但适用于描述符协议。
from functools import partialmethod
class Multiplier:
def __init__(self, factor):
self.factor = factor
def multiply(self, x, y=1):
return x * y * self.factor
# 固定 y=2,得到一个默认乘以两倍因子的方法
double = partialmethod(multiply, y=2)
m = Multiplier(10)
print(m.multiply(3)) # 30
print(m.double(3)) # 60
常见陷阱与注意事项
-
参数顺序决定行为
使用位置参数固定时,这些参数将按顺序传递给原函数,调用时的位置参数将接在后面。def sub(a, b): return a - b # 固定 a=5,调用时传入 b p = partial(sub, 5) print(p(3)) # 2 (5-3) # 如果你想让调用时的参数作为 a,需要固定关键字参数 p2 = partial(sub, b=5) print(p2(10)) # 5 (10-5) -
可变对象作为默认值
如果固定的参数是可变对象(列表、字典等),需要注意所有偏函数调用共享同一个对象实例。def append_to_list(value, target=[]): target.append(value) return target partial_append = partial(append_to_list, target=[]) print(partial_append(1)) # [1] print(partial_append(2)) # [1, 2] <- 不是预期的 [2]这里
partial在定义时只创建了一个列表对象,之后调用都修改同一个列表。若要避免,可以使用lambda或工厂函数。 -
partial对象属性
偏函数对象保留了原函数和固定参数的引用,可通过func、args、keywords属性查看。p = partial(pow, 2, exp=3) print(p.func) # <built-in function pow> print(p.args) # (2,) print(p.keywords) # {'exp': 3}
总结
functools.partial用于固定函数的某些参数,生成一个更特定的新函数。- 它比
lambda更显式、高效,且更易于序列化和调试。 - 适用于减少重复参数、适配回调接口、创建默认行为变体等场景。
- 记住参数的固定顺序与覆盖规则,避免可变对象带来的共享问题。
合理运用偏函数,能让你的 Python 代码更简洁、更具表达力。