跳到主要内容

偏函数

偏函数(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) 的区别正是“创建可复用规则”和“立即得到结果”。

过度绑定和隐藏依赖​

一次固定太多参数会让新函数的用途不明显;关键配置应通过命名变量或工厂函数表达。偏函数也不会复制可变参数对象,若固定的是列表或字典,后续修改该对象会影响调用结果。

练习​

  1. 使用 partial 创建一个始终以 10 为底的整数解析函数。
  2. 将一个接收多个参数的函数适配到 map() 的单参数回调接口。
  3. 使用 partialmethod 为类添加 get 和 post 两个快捷方法。
  4. 比较默认参数、partial 和 lambda 在调试信息与可读性上的差异。

小结​

  • functools.partial 通过预先绑定参数创建新的可调用对象。
  • 位置参数按顺序追加,固定后面的参数时通常使用关键字;调用时可覆盖已保存的关键字。
  • partial 很适合把已有函数适配为回调,逻辑复杂或需要重排参数时应使用 lambda 或 def。
  • 类方法应使用 partialmethod,它会正确处理实例的 self。
  • 偏函数只绑定参数,不改变原函数逻辑;固定可变对象时要留意共享状态。