我想知道这些functools.partial和inspect.signature事实背后的故事--无论是合理的设计还是继承的遗产-- (这里讨论python3.8)。
设立:
from functools import partial
from inspect import signature
def bar(a, b):
return a / b所有的一切都从以下几个方面开始,这似乎符合咖喱标准。我们将a定位为3,a从签名中消失,它的值确实绑定到3
f = partial(bar, 3)
assert str(signature(f)) == '(b)'
assert f(6) == 0.5 == f(b=6)如果我们试图为a指定一个备用值,f将不会告诉我们我们得到了一个意外的关键字,而是它获得了参数a的多个值
f(a=2, b=6) # TypeError: bar() got multiple values for argument 'a'
f(c=2, b=6) # TypeError: bar() got an unexpected keyword argument 'c'但是,如果我们通过关键字修复b=3,则b是从签名中删除的而不是,它是对关键字的类更改,而且我们仍然可以使用它(覆盖默认值,作为正常的默认设置,在前面的情况下a不能这样做):
f = partial(bar, b=3)
assert str(signature(f)) == '(a, *, b=3)'
assert f(6) == 2.0 == f(6, b=3)
assert f(6, b=1) == 6.0为什么这么不对称?
更奇怪的是,我们可以做到:
f = partial(bar, a=3)
assert str(signature(f)) == '(*, a=3, b)' # whaaa?! non-default argument follows default argument?好的:对于只使用关键字的参数,不能混淆默认分配给什么参数,但我仍然想知道这些选择背后有什么设计思想或约束。
发布于 2022-03-09 02:21:52
使用带有位置参数的partial
f = partial(bar, 3)根据设计,在调用函数时,首先分配位置参数。然后,在逻辑上,应该将3分配给带有partial的a。从签名中删除它是有意义的,因为没有办法再给它分配任何东西!
当你拥有f(a=2, b=6)时,你实际上是在做
bar(3, a=2, b=6)当你拥有f(2, 2)时,你实际上是在做
bar (3, 2, 2)我们永远摆脱不了3
对于新的部分函数:
a一个不同的值a给它分配一个不同的值,因为它已经“填充”了如果有一个与关键字名称相同的参数,则将参数值分配给该参数槽。但是,如果参数槽已被填充,则这是一个错误。
为了更好地理解这一问题,我建议阅读佩普-3102的佩普-3102部分。
使用带有关键字参数的partial
f = partial(bar, b=3)这是一个不同的用例。我们将关键字参数应用于bar。
你在功能上转向
def bar(a, b):
...转到
def f(a, *, b=3):
...其中,b变成了只使用关键字的参数,而不是
def f(a, b=3):
...inspect.signature正确地反映了partial的设计决策。传递给partial的关键字参数被设计为附加其他位置参数(来源)。
请注意,此行为不一定覆盖f = partial(bar, b=3)提供的关键字参数,也就是说,无论是否提供第二个位置参数,都将应用b=3 (如果这样做,将有一个TypeError )。这与带有默认值的位置参数不同。
>>> f(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() takes 1 positional argument but 2 were given其中f(1, 2)等价于bar(1, 2, b=3)
重写它的唯一方法是使用关键字参数。
>>> f(2, b=2)一个只能用关键字指定但按位置分配的参数?这是一个只使用关键字的参数。因此,(a, *, b=3)而不是(a, b=3)。
非默认参数的基本原理遵循默认参数。
f = partial(bar, a=3)
assert str(signature(f)) == '(*, a=3, b)' # whaaa?! non-default argument follows default argument?def bar(a=3, b)。a和b被称为positional-or-keyword arguments。def bar(*, a=3, b)。a和b是keyword-only arguments。尽管在语义上,a有一个默认值,因此它是可选的,但我们不能让它不被赋值,因为如果我们想要在位置上使用b,则需要为b (即positional-or-keyword argument )分配一个值。如果我们不为a提供一个值,就必须使用b作为keyword argument。
去死吧!b不可能像我们所期望的那样成为positional-or-keyword argument。
纯正电子参数的PEP也在某种程度上展示了其背后的原理。
这也与前面提到的“函数调用行为”有关。
partial !=运行和实现详细信息
partial通过其实现封装原始函数,同时存储传递给它的固定参数。
IT不是通过运行来实现的。相反,它是部分应用程序,而不是函数编程意义上的运行。partial首先应用固定参数,然后应用包装器调用的参数:
def __call__(self, /, *args, **keywords):
keywords = {**self.keywords, **keywords}
return self.func(*self.args, *args, **keywords)这就解释了f(a=2, b=6) # TypeError: bar() got multiple values for argument 'a'。
另见:curry
在inspect的罩下
视察的输出是另一回事。
inspect本身是一个产生用户友好输出的工具。特别是对于partial() (和partialmethod(),类似地),它遵循包装函数,同时考虑到固定参数:
if isinstance(obj, functools.partial):
wrapped_sig = _get_signature_of(obj.func)
return _signature_get_partial(wrapped_sig, obj)请注意,inspect.signature的目标不是在AST中显示包装函数的实际签名。
def _signature_get_partial(wrapped_sig, partial, extra_args=()):
"""Private helper to calculate how 'wrapped_sig' signature will
look like after applying a 'functools.partial' object (or alike)
on it.
"""
...因此,我们为f = partial(bar, 3)提供了一个很好的、理想的签名,但在现实中得到了f(a=2, b=6) # TypeError: bar() got multiple values for argument 'a'。
后续行动
如果您非常想要运行,那么如何在Python中实现它,从而给出预期的TypeError
发布于 2022-03-08 23:59:46
当您向partial提供位置或关键字参数时,将构造新函数。
F=部分(bar,3) f(a=2,b=6) # TypeError: bar()获得参数'a‘的多个值 f(c=2,b=6) # TypeError: bar()得到一个意外的关键字参数'c‘
这实际上与partial的思想是一致的,即通过添加传递给partial的位置和关键字参数将参数传递给包装函数。
这些案件的表现与预期相符:
bar(3, a=2, b=6) # TypeError: bar() got multiple values for argument 'a'
bar(3, c=2, b=6) # TypeError: bar() got an unexpected keyword argument 'c'但是现在,如果我们通过a关键字修复b=3,则没有从签名中删除b, F=部分(bar,b=3) 断言str(签名(F)) == '(a,*,b=3)‘ 断言f(6) == 2.0 == f(6,b=3) 断言f(6,b=1) == 6.0
这种情况不同于上面的情况,因为在前一种情况中,位置参数是提供给partial的,而不是关键字参数。当向partial提供位置参数时,从签名中删除它们是有意义的。作为关键字提供的参数不会从签名中删除。
到目前为止,没有不一致或不对称。
F=部分(bar,a=3) 断言str(签名(F)) == '(*,a=3,b)‘# whaaa?!非默认参数跟随默认参数?
这里的签名是有意义的,也是partial(bar, a=3)的期望--它的工作原理与def f(*, a=3, b): ...一样,在本例中是正确的签名。请注意,在本例中将a=3提供给partial时,a成为只关键字的参数,b也是如此。
这是因为当位置参数作为关键字提供时,必须指定以下所有参数。
sig = signature(f)
sig.parameters['a'].kind # <_ParameterKind.KEYWORD_ONLY: 3>inspect.getfullargspec(f)
# FullArgSpec(args=[], varargs=None, varkw=None, defaults=None, kwonlyargs=['a', 'b'], kwonlydefaults={'a': 3}, annotations={})https://stackoverflow.com/questions/71402387
复制相似问题