Python偏函数partial的用法小结
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">什么是partial?</a></li><li><a href="#_label1">1) 基本用法与参数合并规则</a></li><li><a href="#_label2">2) 配合标准库:map/sorted/reduce等“柯里化”场景</a></li><li><a href="#_label3">3) 回调函数需要“额外上下文”——用partial传额外参数</a></li><li><a href="#_label4">4) 装饰器/工厂的参数化:让“可调用签名更好看”</a></li><li><a href="#_label5">5)partialvslambda:各有优劣</a></li><li><a href="#_label6">6) 深入属性与调试</a></li><li><a href="#_label7">7) 与实例方法的细节:partialvspartialmethod</a></li><li><a href="#_label8">8) 与functools.update_wrapper的配合(可选)</a></li><li><a href="#_label9">9) 与并发库(concurrent.futures/multiprocessing)的实战</a></li><li><a href="#_label10">10) 在日志/打印等“固定上下文”的场景</a></li><li><a href="#_label11">11) 常见误区与最佳实践</a></li><li><a href="#_label12">小结</a></li></ul></div><p>本篇博客介绍Python偏函数partial的用法。</p><p class="maodian"><a name="_label0"></a></p><h2>什么是partial?</h2>
<p>functools.partial(func, /, *args, **keywords) 会返回一个新可调用对象,它把原函数 func 的部分位置参数和/或关键字参数“预先绑定”。<br />这样你就能得到一个“定制版”的函数,后续只需要补齐剩余参数即可调用。</p>
<ul><li>返回对象类型是 functools.partial 实例,但和函数用法相同(可调用)。</li><li>它拥有属性:p.func(原函数)、p.args(预绑定位置参数)、p.keywords(预绑定关键字参数)。</li></ul>
<p class="maodian"><a name="_label1"></a></p><h2>1) 基本用法与参数合并规则</h2>
<div class="jb51code"><pre class="brush:py;">from functools import partial
def power(base, exp, *, mod=None):
res = base ** exp
return res if mod is None else res % mod
# 1.1 预绑定部分位置参数
square = partial(power, exp=2) # 固定指数
print(square(3)) # 9
print(square(3, mod=5)) # 4
# 1.2 预绑定关键字参数
cube_mod_7 = partial(power, exp=3, mod=7)
print(cube_mod_7(2)) # 1(8 % 7)
# 1.3 后续调用的关键字**可以覆盖**先前绑定的关键字
p = partial(power, exp=2, mod=5)
print(p(3)) # 4 (9 % 5)
print(p(3, mod=None)) # 9 —— 覆盖为 None
# 1.4 后续调用的**位置参数不能“挪位”覆盖**已绑定的位置参数
mul = lambda a, b, c: (a, b, c)
p2 = partial(mul, 10) # a=10 已固定
print(p2(20, 30)) # (10, 20, 30)
# p2(5, a=1) -> TypeError: a 给了多个值(不允许)
</pre></div>
<p class="maodian"><a name="_label2"></a></p><h2>2) 配合标准库:map/sorted/reduce等“柯里化”场景</h2>
<div class="jb51code"><pre class="brush:py;">from functools import partial, reduce
from operator import mul
nums =
# 2.1 map:把二元函数“变成一元”
double = partial(mul, 2) # 固定左操作数
print(list(map(double, nums))) #
# 2.2 sorted:固定 key / reverse
students = [{"name":"A", "age":20}, {"name":"B", "age":18}]
by = partial(sorted, key=lambda x: x["age"])
print(by(students)) # 按 age 升序
by_desc = partial(sorted, key=lambda x: x["age"], reverse=True)
print(by_desc(students))
# 2.3 reduce:固定初始值
sum_from_10 = partial(reduce, lambda a, b: a + b, initial=10)
print(sum_from_10(nums)) # 20
</pre></div>
<p class="maodian"><a name="_label3"></a></p><h2>3) 回调函数需要“额外上下文”——用partial传额外参数</h2>
<p>这在 GUI(PySide6/Qt)、异步回调、信号、钩子、线程池回调里非常常见。</p>
<div class="jb51code"><pre class="brush:py;">from functools import partial
import asyncio, concurrent.futures, time
def on_done(label, fut: concurrent.futures.Future):
print(f"[{label}] result ->", fut.result())
def heavy(x):
time.sleep(0.2)
return x * x
async def main():
loop = asyncio.get_running_loop()
with concurrent.futures.ThreadPoolExecutor() as pool:
fut = loop.run_in_executor(pool, heavy, 9)
# 给回调多传一个 label
fut.add_done_callback(partial(on_done, "task#1"))
# 也可以 await 等它
print("await:", await fut)
asyncio.run(main())
</pre></div>
<blockquote><p>Qt 场景下,button.clicked.connect(partial(handler, extra_arg)) 也很实用(用来把行号/模型索引等传给槽函数)。</p></blockquote>
<p class="maodian"><a name="_label4"></a></p><h2>4) 装饰器/工厂的参数化:让“可调用签名更好看”</h2>
<p>用 <code>partial</code> 做“带参数的装饰器”或“工厂函数”非常自然:</p>
<div class="jb51code"><pre class="brush:py;">from functools import wraps, partial
def _retry_impl(func, attempts, delay):
@wraps(func)
def wrapper(*args, **kwargs):
last = None
for _ in range(attempts):
try:
return func(*args, **kwargs)
except Exception as e:
last = e
time.sleep(delay)
raise last
return wrapper
# 用 partial 固定 attempts/delay,得到一个“可当装饰器用”的可调用
import time
retry3 = partial(_retry_impl, attempts=3, delay=0.1)
@retry3
def flaky():
print("try...")
if time.time() % 2 < 1:
raise ValueError("boom")
return "ok"
print("flaky ->", flaky())
</pre></div>
<p class="maodian"><a name="_label5"></a></p><h2>5)partialvslambda:各有优劣</h2>
<ul><li>partial:可读性好、可查看 p.func/p.args/p.keywords、可 picklable(常用于并发/进程池)。</li><li>lambda:最灵活(能改变参数顺序、做简单计算),但不可 introspect、某些场景不可序列化。</li></ul>
<div class="jb51code"><pre class="brush:py;">from functools import partial
def f(a, b, c): return (a, b, c)
# partial 只能“从左到右”补位置参数(或直接用关键字)
g1 = partial(f, 1) # ==> f(1, b, c)
# lambda 可自由重排
g2 = lambda b, c: f(1, c, b) # 调换了 b/c 的位置
print(g1(2, 3), g2(2, 3))
</pre></div>
<p class="maodian"><a name="_label6"></a></p><h2>6) 深入属性与调试</h2>
<div class="jb51code"><pre class="brush:py;">from functools import partial
def greet(greet_word, name, punctuation="!"):
return f"{greet_word}, {name}{punctuation}"
hi_tom = partial(greet, "Hi", "Tom", punctuation=".")
print(hi_tom()) # Hi, Tom.
print(hi_tom.func) # 原函数 <function greet ...>
print(hi_tom.args) # ('Hi', 'Tom')
print(hi_tom.keywords) # {'punctuation': '.'}
# 可覆盖同名关键字
print(hi_tom(punctuation="!!!")) # Hi, Tom!!!
</pre></div>
<p class="maodian"><a name="_label7"></a></p><h2>7) 与实例方法的细节:partialvspartialmethod</h2>
<ul><li><code>partial</code> 用在<strong>函数或绑定方法</strong>都可以。</li><li>但如果你在<strong>类定义里</strong>想创建“半绑定方法”,要用 <code>functools.partialmethod</code>,它会正确处理 <code>self</code> 的绑定(描述符行为)。</li></ul>
<div class="jb51code"><pre class="brush:py;">from functools import partialmethod
class Logger:
def log(self, level, msg):
print(f"[{level}] {self.name}: {msg}")
debug = partialmethod(log, "DEBUG") # 等价于 def debug(self, msg): return self.log("DEBUG", msg)
info= partialmethod(log, "INFO")
def __init__(self, name):
self.name = name
l = Logger("core")
l.debug("hello") # core: hello
l.info("world") # core: world
</pre></div>
<blockquote><p>若用 partial(log, "DEBUG") 直接赋给类属性,self 不会自动绑定,调用会报错;partialmethod 才能正确作为方法(描述符)工作。</p></blockquote>
<p class="maodian"><a name="_label8"></a></p><h2>8) 与functools.update_wrapper的配合(可选)</h2>
<p><code>partial</code> 本身不是函数对象,若你需要较好地保留原函数的 <code>__name__</code>、<code>__doc__</code> 等用于文档/帮助,可包一层简单函数并使用 <code>update_wrapper</code>:</p>
<div class="jb51code"><pre class="brush:py;">from functools import partial, update_wrapper
def power(a, b): return a ** b
square = partial(power, b=2)
def as_func(p):
def wrapper(*args, **kwargs):
return p(*args, **kwargs)
return update_wrapper(wrapper, p.func)
square_fn = as_func(square)
print(square_fn.__name__, square_fn.__doc__) # 继承了 power 的元数据
</pre></div>
<p class="maodian"><a name="_label9"></a></p><h2>9) 与并发库(concurrent.futures/multiprocessing)的实战</h2>
<p><code>partial</code> 往往比 <code>lambda</code> 更容易被序列化,适合提交到进程池。</p>
<div class="jb51code"><pre class="brush:py;">from functools import partial
from concurrent.futures import ProcessPoolExecutor, as_completed
def area(w, h, scale=1.0):
return w * h * scale
if __name__ == "__main__":
scaled_area = partial(area, scale=0.5)# 可被 pickle
items = [(10, 20), (3, 4), (6, 7)]
with ProcessPoolExecutor() as ex:
futs =
for f in as_completed(futs):
print("area:", f.result())
</pre></div>
<p class="maodian"><a name="_label10"></a></p><h2>10) 在日志/打印等“固定上下文”的场景</h2>
<div class="jb51code"><pre class="brush:py;">from functools import partial
print_info = partial(print, "") # 固定前缀
print_warn = partial(print, "")
print_info("system started")
print_warn("disk almost full")
</pre></div>
<p class="maodian"><a name="_label11"></a></p><h2>11) 常见误区与最佳实践</h2>
<ol><li>不能使用“占位符”来跳过中间某个位置参数(stdlib 没有这个特性)。<br />需要时用关键字参数或 lambda 重排。</li><li>重复提供同名位置参数会报错(“给了多个值”);重复关键字后者覆盖前者。</li><li>不要把可变对象当作“默认值状态”去修改(例如绑定 list 后在原函数里修改它),除非你就是有意为之;partial 持有的引用和普通默认参数一样需要小心共享状态。</li><li>在框架/回调里,优先用 partial 传递额外上下文,比 lambda 更“可调试/可序列化”。</li></ol>
<p class="maodian"><a name="_label12"></a></p><h2>小结</h2>
<ul><li><code>partial</code>:给函数“预装参数”,写出更简洁的 API/回调。</li><li><code>partialmethod</code>:在类中定义“半绑定方法”,正确处理 <code>self</code>。</li><li>常见场景:回调传参、并发任务/进程池、排序/映射的柯里化、日志前缀、装饰器参数化。</li></ul>
頁:
[1]