Python偏函數(shù)partial的用法小結(jié)
本篇博客介紹Python偏函數(shù)partial的用法。
什么是partial?
functools.partial(func, /, *args, **keywords) 會(huì)返回一個(gè)新可調(diào)用對(duì)象,它把原函數(shù) func 的部分位置參數(shù)和/或關(guān)鍵字參數(shù)“預(yù)先綁定”。
這樣你就能得到一個(gè)“定制版”的函數(shù),后續(xù)只需要補(bǔ)齊剩余參數(shù)即可調(diào)用。
- 返回對(duì)象類型是 functools.partial 實(shí)例,但和函數(shù)用法相同(可調(diào)用)。
- 它擁有屬性:p.func(原函數(shù))、p.args(預(yù)綁定位置參數(shù))、p.keywords(預(yù)綁定關(guān)鍵字參數(shù))。
1) 基本用法與參數(shù)合并規(guī)則
from functools import partial
def power(base, exp, *, mod=None):
res = base ** exp
return res if mod is None else res % mod
# 1.1 預(yù)綁定部分位置參數(shù)
square = partial(power, exp=2) # 固定指數(shù)
print(square(3)) # 9
print(square(3, mod=5)) # 4
# 1.2 預(yù)綁定關(guān)鍵字參數(shù)
cube_mod_7 = partial(power, exp=3, mod=7)
print(cube_mod_7(2)) # 1 (8 % 7)
# 1.3 后續(xù)調(diào)用的關(guān)鍵字**可以覆蓋**先前綁定的關(guān)鍵字
p = partial(power, exp=2, mod=5)
print(p(3)) # 4 (9 % 5)
print(p(3, mod=None)) # 9 —— 覆蓋為 None
# 1.4 后續(xù)調(diào)用的**位置參數(shù)不能“挪位”覆蓋**已綁定的位置參數(shù)
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 給了多個(gè)值(不允許)
2) 配合標(biāo)準(zhǔn)庫(kù):map/sorted/reduce等“柯里化”場(chǎng)景
from functools import partial, reduce
from operator import mul
nums = [1, 2, 3, 4]
# 2.1 map:把二元函數(shù)“變成一元”
double = partial(mul, 2) # 固定左操作數(shù)
print(list(map(double, nums))) # [2, 4, 6, 8]
# 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
3) 回調(diào)函數(shù)需要“額外上下文”——用partial傳額外參數(shù)
這在 GUI(PySide6/Qt)、異步回調(diào)、信號(hào)、鉤子、線程池回調(diào)里非常常見(jiàn)。
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)
# 給回調(diào)多傳一個(gè) label
fut.add_done_callback(partial(on_done, "task#1"))
# 也可以 await 等它
print("await:", await fut)
asyncio.run(main())
Qt 場(chǎng)景下,button.clicked.connect(partial(handler, extra_arg)) 也很實(shí)用(用來(lái)把行號(hào)/模型索引等傳給槽函數(shù))。
4) 裝飾器/工廠的參數(shù)化:讓“可調(diào)用簽名更好看”
用 partial 做“帶參數(shù)的裝飾器”或“工廠函數(shù)”非常自然:
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,得到一個(gè)“可當(dāng)裝飾器用”的可調(diào)用
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())
5)partialvslambda:各有優(yōu)劣
- partial:可讀性好、可查看 p.func/p.args/p.keywords、可 picklable(常用于并發(fā)/進(jìn)程池)。
- lambda:最靈活(能改變參數(shù)順序、做簡(jiǎn)單計(jì)算),但不可 introspect、某些場(chǎng)景不可序列化。
from functools import partial def f(a, b, c): return (a, b, c) # partial 只能“從左到右”補(bǔ)位置參數(shù)(或直接用關(guān)鍵字) g1 = partial(f, 1) # ==> f(1, b, c) # lambda 可自由重排 g2 = lambda b, c: f(1, c, b) # 調(diào)換了 b/c 的位置 print(g1(2, 3), g2(2, 3))
6) 深入屬性與調(diào)試
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) # 原函數(shù) <function greet ...>
print(hi_tom.args) # ('Hi', 'Tom')
print(hi_tom.keywords) # {'punctuation': '.'}
# 可覆蓋同名關(guān)鍵字
print(hi_tom(punctuation="!!!")) # Hi, Tom!!!
7) 與實(shí)例方法的細(xì)節(jié):partialvspartialmethod
partial用在函數(shù)或綁定方法都可以。- 但如果你在類定義里想創(chuàng)建“半綁定方法”,要用
functools.partialmethod,它會(huì)正確處理self的綁定(描述符行為)。
from functools import partialmethod
class Logger:
def log(self, level, msg):
print(f"[{level}] {self.name}: {msg}")
debug = partialmethod(log, "DEBUG") # 等價(jià)于 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") # [DEBUG] core: hello
l.info("world") # [INFO] core: world
若用 partial(log, "DEBUG") 直接賦給類屬性,self 不會(huì)自動(dòng)綁定,調(diào)用會(huì)報(bào)錯(cuò);partialmethod 才能正確作為方法(描述符)工作。
8) 與functools.update_wrapper的配合(可選)
partial 本身不是函數(shù)對(duì)象,若你需要較好地保留原函數(shù)的 __name__、__doc__ 等用于文檔/幫助,可包一層簡(jiǎn)單函數(shù)并使用 update_wrapper:
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 的元數(shù)據(jù)
9) 與并發(fā)庫(kù)(concurrent.futures/multiprocessing)的實(shí)戰(zhàn)
partial 往往比 lambda 更容易被序列化,適合提交到進(jìn)程池。
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 = [ex.submit(scaled_area, w, h) for (w, h) in items]
for f in as_completed(futs):
print("area:", f.result())
10) 在日志/打印等“固定上下文”的場(chǎng)景
from functools import partial
print_info = partial(print, "[INFO]") # 固定前綴
print_warn = partial(print, "[WARN]")
print_info("system started")
print_warn("disk almost full")
11) 常見(jiàn)誤區(qū)與最佳實(shí)踐
- 不能使用“占位符”來(lái)跳過(guò)中間某個(gè)位置參數(shù)(stdlib 沒(méi)有這個(gè)特性)。
需要時(shí)用關(guān)鍵字參數(shù)或 lambda 重排。 - 重復(fù)提供同名位置參數(shù)會(huì)報(bào)錯(cuò)(“給了多個(gè)值”);重復(fù)關(guān)鍵字后者覆蓋前者。
- 不要把可變對(duì)象當(dāng)作“默認(rèn)值狀態(tài)”去修改(例如綁定 list 后在原函數(shù)里修改它),除非你就是有意為之;partial 持有的引用和普通默認(rèn)參數(shù)一樣需要小心共享狀態(tài)。
- 在框架/回調(diào)里,優(yōu)先用 partial 傳遞額外上下文,比 lambda 更“可調(diào)試/可序列化”。
小結(jié)
partial:給函數(shù)“預(yù)裝參數(shù)”,寫出更簡(jiǎn)潔的 API/回調(diào)。partialmethod:在類中定義“半綁定方法”,正確處理self。- 常見(jiàn)場(chǎng)景:回調(diào)傳參、并發(fā)任務(wù)/進(jìn)程池、排序/映射的柯里化、日志前綴、裝飾器參數(shù)化。
到此這篇關(guān)于Python偏函數(shù)partial的用法小結(jié)的文章就介紹到這了,更多相關(guān)Python偏函數(shù)partial內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
對(duì)python中的for循環(huán)和range內(nèi)置函數(shù)詳解
下面小編就為大家分享一篇對(duì)python中的for循環(huán)和range內(nèi)置函數(shù)詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-04-04
利用Python實(shí)現(xiàn)普通視頻變成動(dòng)漫視頻
這篇文章主要為大家詳細(xì)介紹了如何利用Python語(yǔ)言實(shí)現(xiàn)普通視頻變成動(dòng)漫視頻效果,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-08-08
一文詳解如何在Python中從字符串中提取部分內(nèi)容
這篇文章主要介紹了如何在Python中從字符串中提取部分內(nèi)容的相關(guān)資料,包括使用正則表達(dá)式、Pyparsing庫(kù)、AST(抽象語(yǔ)法樹)、字符串操作和函數(shù)式編程,每種方法都有其適用場(chǎng)景和優(yōu)缺點(diǎn),需要的朋友可以參考下2025-04-04
Matplotlib實(shí)戰(zhàn)之柱狀圖繪制詳解
柱狀圖,是一種使用矩形條,對(duì)不同類別進(jìn)行數(shù)值比較的統(tǒng)計(jì)圖表,這篇文章主要為大家詳細(xì)介紹了如何使用Matplotlib繪制柱狀圖,需要的可以參考下2023-08-08
Python Sqlalchemy如何實(shí)現(xiàn)select for update
這篇文章主要介紹了Python Sqlalchemy如何實(shí)現(xiàn)select for update,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10

