乐于分享
好东西不私藏

Python核心工具篇:functools.partial 用法详解

Python核心工具篇:functools.partial 用法详解

很多同学在写Python代码时,常会遇到这样的场景:某个函数需要频繁调用,但每次调用时,部分参数都是固定的;或者暂时拿不到所有参数,想先“保存”已有的参数,后续再补充调用。这候,functools.partial 就能派上大用场了。

partial 是 functools 模块中的一个函数,它接收一个可调用对象(比如函数)和若干参数(位置参数/关键字参数),返回一个“部分对象”(partial object)。让我看看基础用法:

from functools import partial# 定义一个接收多参数的函数# x、y是必选位置参数,*z是可变位置参数,k是关键字参数def foo(x, y, *z, k=0):    print(x, y, z, k)# 1. 常规调用(无partial):每次都要传入所有必要参数foo(1234, k=5)# 输出:1 2 (3, 4) 5# 2. 创建partial对象:先冻结第一个参数x=1p = partial(foo, 1)# 3. 继续冻结第二个参数y=2(可以多次对partial对象叠加参数)p = partial(p, 2)# 4. 调用partial对象(此时还没传z和k,用默认值)p()# 输出:1 2 () 0# 5. 继续冻结可变参数z=3、4p = partial(p, 34)  # 也可以写成 partial(p, *(34))p()# 输出:1 2 (3, 4) 0# 6. 冻结关键字参数k=5p = partial(p, k=5)p()# 输出:1 2 (3, 4) 5

这里有两个关键注意点:1.可以多次对 partial 对象叠加参数,每次叠加的参数,都会被“保存”下来,最终一起传递给原函数。2.partial 对象可以多次调用,不会像生成器那样调用一次就失效——每次调用,都会用保存的参数+新传入的参数(如果有)执行原函数。

在实际开发中,比如我们用FastAPI写接口时,常会有版本前缀的需求,例如/api/v1下的所有接口,都需要加前缀和权限校验依赖,而健康检查、状态查询等不需要。如果每个接口都写一遍app.include_router(router,prefix=”/api/v1″,dependencies=[…])会很冗余,用partial固定前缀和依赖实现:

from functools import partialfrom fastapi import Depends, FastAPIfrom app.api import depsfrom app.api.api_v1.routers import inventory, orders, usersfrom app.api.probing.routers import probesAPP_VERSION = "v1"ROUTE_PREFIX = f"/api/{APP_VERSION}"app = FastAPI(    title="My Web App",    description="这是一个基于FastAPI的Web应用",    version=APP_VERSION,)# 不需要前缀的路由(直接调用)app.include_router(probes.router, tags=["probes"])# 用partial固定前缀和权限依赖route_include = partial(    app.include_router,    prefix=ROUTE_PREFIX,    dependencies=[Depends(deps.authorize)]  # 所有v1接口都需要权限校验)# 后续添加v1接口,直接调用partial对象即可route_include(users.router, tags=["users"])route_include(orders.router, tags=["orders"])route_include(inventory.router, tags=["inventory"])

是不是很简单,但使用partial时候也要注意,partial对象不等于函数对象,partial 对象不能直接访问原函数的 __name__(函数名)和 __doc__(文档字符串),需要通过 .func 属性访问原函数,再获取这些属性

def foo(x, y):    """这是一个测试函数"""    return x, yp = partial(foo, 1)# 错误:直接访问会报错或返回partial相关信息# print(p.__name__)# 正确:通过.func访问原函数print(p.func.__name__)  # 输出:fooprint(p.func.__doc__)   # 输出:这是一个测试函数

创建 partial 对象时,即使没传完原函数的必选参数,也不会报错——报错只会在你调用 partial 对象、且仍未传完必选参数时出现:

def foo(x, y):  # x和y都是必选参数    return x, y# 创建partial时,只传x=1,未传y,不报错p = partial(foo, 1)# 调用partial时,仍未传y,报错(缺少必选参数y)p()  # TypeError: foo() missing 1 required positional argument: 'y'

如果 partial 对象已经保存了原函数的所有必选参数,后续调用时,再传位置参数会报错:

def foo(x, y):    return x, y# 传完所有必选参数x=1、y=2p = partial(foo, 12)p()  # 正常输出:(12)# 错误:再传位置参数3,会报错(参数过多)p(3)  # TypeError: foo() takes 2 positional arguments but 3 were given# 正确:可以传关键字参数(覆盖原有值)p(y=3)  # 输出:(13)

如果想在类中定义一个“固定部分参数”的方法,直接用 partial 会报错——因为 partial 不会自动传递类的实例(self)。这时候需要用 functools.partialmethod 替代

from functools import partial, partialmethodclass Foo:    def __init__(self, x):        self.x = x        self.y = {}    def add_to_y(self, **kwargs):        self.y.update(kwargs)    # 错误:用partial定义类方法,调用时会报错(缺少self)    # p = partial(add_to_y, foo="bar")    # 正确:用partialmethod定义类方法    p = partialmethod(add_to_y, foo="bar")f = Foo(1)f.p()  # 正常执行print(f.y)  # 输出:{'foo': 'bar'}
总之,partial 不是万能的,不要为了用而用。如果只是简单的临时封装(比如只固定一个参数),lambda 可能更简洁;但如果是多次复用、需要叠加参数,partial 绝对是更优选择。
本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » Python核心工具篇:functools.partial 用法详解

猜你喜欢

  • 暂无文章