基本上,我想做的是:
>>> from functools import partial
>>> partial(str.startswith, prefix='a')('a')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: startswith() takes no keyword arguments
但更常见的问题是,如何使用partial
填充特定的位置参数。
P.S。我确实知道我可以改用lambda
。
答案 0 :(得分:43)
无法做到。你必须做一个包装函数。
表面上看,你会尝试使用关键字参数 - 这就是它们的用途,对吧?不幸的是,正如您所发现的那样,python的标准库函数do not take named parameters。因此,在没有使另一个函数运行干扰的情况下,partial
的当前实现是不可能的。
根据对PEP 309的接受,接受包含的是“部分()类型构造函数绑定最左侧位置参数和任何关键字。”此外,它声明:
请注意,当对象被调用时,就好像它是一个函数一样,位置参数附加到提供给构造函数的位置参数,关键字参数覆盖并扩充提供给构造函数的参数。
在创建对象和调用对象时,可以提供位置参数,关键字参数或两者。
因为附加位置参数追加,所以无法提供一些先前的位置参数(通过此实现)。
然而,它继续:
部分应用正确中的参数,或在任意位置插入参数 会产生自己的问题,但是等待发现良好的实现和非混淆的语义,我不会我认为应该排除它。
所以,它显然可能会出现在桌面上,但就目前而言,它并没有那样实现。
为了披露,上面引用的重点是我自己的。
答案 1 :(得分:5)
我知道这很旧,但我一直遇到这个问题,我想我已经创建了一个巧妙的解决方案。如果您在构建对象时不知道它,我使用 partial
的一个稍微修改的版本,它使用一个 Ellipses 对象 (...
) 作为占位符值。这对这个非常有用!
这是__call__
的原始partial
方法
def __call__(self, /, *args, **keywords):
keywords = {**self.keywords, **keywords}
return self.func(*self.args, *args, **keywords)
相反,我们可以使用文字 ...
作为特殊情况来指示占位符值
>>> type(...)
<class 'ellipsis'>
这是整个实现:
class bind(partial):
"""
An improved version of partial which accepts Ellipsis (...) as a placeholder
"""
def __call__(self, *args, **keywords):
keywords = {**self.keywords, **keywords}
iargs = iter(args)
args = (next(iargs) if arg is ... else arg for arg in self.args)
return self.func(*args, *iargs, **keywords)
使用相当简单
def foo(a, b, c, /, *, d):
print(f"A({a}) B({b}) C({c}) D({d})")
f1 = bind(foo, 1, 2, 3, d=4)
f1()
f2 = bind(foo, 1, 2, d=4)
f2(3)
f3 = bind(foo, 1, ..., 3, d=4)
f3(2)
f4 = bind(foo, ..., 2, ..., d=4)
f4(1, 3)
f5 = bind(foo, ..., d=5)
f5(1, 2, 3, d=4)
答案 2 :(得分:4)
如果您确实需要,可以使用funcy
第三方库中的rpartial。
它的代码在这里:
def rpartial(func, *args):
return lambda *a: func(*(a + args))
因此,您的案件可以按如下方式处理:
>>> startswith_a = rpartial(str.startswith, 'a')
>>> startswith_a('abc')
True
>>> startswith_a('def')
False
答案 3 :(得分:0)
使用此代码:
# See: functoolspartial for binding...
class Function(object):
def __init__(self, fn):
self.fn = fn
def __call__(self, *args, **kwargs):
return self.fn(*args, **kwargs)
def __add__(self, other):
def subfn(*args, **kwargs):
return self(*args, **kwargs) + other(*args, **kwargs)
return subfn
class arg(object):
"""Tagging class"""
def __init__(self, index):
self.index = index
class bind(Function):
"""Reorder positional arguments.
Eg: g = f('yp', _1, 17, _0, dp=23)
Then g('a', 'b', another=55) --> f('yp', 'b', 17, 'a', dp=23, another=55)
"""
def __init__(self, fn, *pargs, **pkwargs):
# Maximum index referred to by the user.
# Inputs to f above this index will be passed through
self.fn = fn
self.pargs = pargs
self.pkwargs = pkwargs
self.max_gindex = max(
(x.index if isinstance(x, arg) else -1 for x in pargs),
default=-1)
def __call__(self, *gargs, **gkwargs):
fargs = \
[gargs[x.index] if isinstance(x, arg) else x for x in self.pargs] + \
list(gargs[self.max_gindex+1:])
fkwargs = dict(self.pkwargs)
fkwargs.update(gkwargs) # Overwrite keys
return self.fn(*fargs, *fkwargs)
然后尝试一下:
def myfn(a,b,c):
print(a,b,c)
bind(myfn, arg(1), 17, arg(0))(19,14)