【问题标题】:How to specify arg position for functool partial()如何为 functool partial() 指定 arg 位置
【发布时间】:2020-03-02 20:59:01
【问题描述】:

根据手册,functoolspartial() '用于部分函数应用程序,它“冻结”函数参数和/或关键字的某些部分,从而产生具有简化签名的新对象。'

指定希望评估的参数位置的最佳方法是什么?

编辑

注意,根据 cmets,要部分评估的函数可能包含命名和未命名参数(这些函数应该是完全任意的,并且可能是预先存在的)

结束编辑

例如,考虑:

def f(x,y,z):
    return x + 2*y + 3*z 

然后,使用

from functools import partial

两者

partial(f,4)(5,6)

partial(f,4,5)(6)

给 32。

但是如果想要评估,比如第三个参数z 或第一个和第三个参数xz,该怎么办?

有没有一种方便的方法可以将位置信息传递给partial,使用decoratordict,其键是所需的arg 位置,相应的值是arg 值?例如传递xz 位置,如下所示:

partial_dict(f,{0:4,2:6})(5)

【问题讨论】:

  • 听起来像是 XY 问题。在这种情况下使用关键字,而不是位置。第三个参数很简单:只需使用 partial(f,z=4) 并在调用时将 x、y 参数作为位置传递。
  • @Jean-FrançoisFabre,没有参数没有命名 - 查看示例中f 的定义,并且不能假设它们被命名,因为函数可能是预先存在的。将编辑 Q 以澄清。
  • @alancalvitti 你能澄清一下“未命名的论点”是什么意思吗?您是指没有参数名称、positional-only 参数或可变参数*args 样式参数的 C API 定义函数吗?除了这些特殊情况,Python 中所有常规定义的函数都可以使用关键字参数代替位置参数来调用。
  • @Brian,没有参数名称,就像上面示例中的f。我认为它不适用于*arg 样式参数,但它也应该适用于命名参数。
  • @alancalvitti 我看到三个参数名称,xyz...

标签: python lambda decorator currying


【解决方案1】:

不,partial 并非旨在将位置参数冻结在非顺序位置。

要实现您的问题中概述的所需行为,您必须提出自己的包装函数,如下所示:

def partial_positionals(func, positionals, **keywords):
    def wrapper(*args, **kwargs):
        arg = iter(args)
        return func(*(positionals[i] if i in positionals else next(arg)
            for i in range(len(args) + len(positionals))), **{**keywords, **kwargs})
    return wrapper

这样:

def f(x, y, z):
    return x + 2 * y + 3 * z

print(partial_positionals(f, {0: 4, 2: 6})(5))

输出:

32

【讨论】:

  • 我已经更新了答案,允许为自定义 partial 函数指定默认关键字参数,正如您的评论所建议的那样。
  • 谢谢,我接受了。但我还想问一下,您是否认为有更方便的语法方法来指定所持有的参数,即不评估。例如,在另一种语言(Mathematica)中,您可以使用像 f(x,•,z) 这样的项目符号点(选项 8)将该函数转换为部分评估的 f(x,z)。我认为这在 python 中是不可行的,但也许有类似的方法?
  • 不客气。我不认为有任何内置的东西接近 Python 中的用法。您想要实现的目标通常是通过为 partial 函数指定关键字参数来更符合 Python 的要求。位置参数意味着在 Python 中按顺序指定。
  • 我添加了一个新答案,以根据 Mathematica 风格的部分参数规范定制解决方案。
  • 关于“你想要的是......通过指定关键字参数以 Python 方式完成”。不,这不是我的本意。我想我需要重新表述这个问题。我正在为任何函数寻找通用的部分评估解决方案,包括预先存在的函数,这些函数可能有也可能没有关键字 args,即使有,您通常也希望保留一个非关键字 arg。
【解决方案2】:

只需使用关键字参数。使用上面对f 的定义,

>>> g = partial(f, z=10)
>>> g(2, 4)
40
>>> h = partial(f, y=4, z=10)
>>> h(2)
40

请注意,一旦对给定参数使用关键字参数,您必须对所有剩余参数使用关键字参数。例如,以下内容无效:

>>> j = partial(f, x=2, z=10)
>>> j(4)
TypeError: f() got multiple values for argument 'x'

但继续使用关键字参数是:

>>> j = partial(f, x=2, z=10)
>>> j(y=4)
40

当您使用functools.partial 时,您存储*args**kwargs 的值以供以后插值使用。当您稍后调用“部分应用”函数时,functools.partial 的实现有效地将先前提供的*args**kwargs 分别添加到前端和末尾的参数列表中,就好像您已经插入了这些参数解包你自己。即调用

h = partial(1, z=10)
f(4)

大致相当于写

args = [1]
kwargs = {'z': 10}
f(*args, 4, **kwargs)

因此,您如何向functools.partial 提供参数的语义与您需要在上面的argskwargs 变量中存储参数的方式相同,以便最终调用f 是明智的.有关更多信息,请查看functools module documentation 中给出的functools.partial 的伪实现

【讨论】:

  • 我正在寻找一个通用解决方案,无论是命名参数还是未命名参数 - 请参阅我对 Jean-Francois 的 cmets 以及随后在我的问题中的澄清。
  • @alancalvitti 我的回答的后半部分解决了一般情况。您传递给functools.partial 的所有内容都将表现得就像您以上面提供的样式手动存储和解包参数一样。如果您不能使用关键字参数(由于我在上面对您的问题的评论中提到的三个原因之一),您可能需要定义一个可以部分评估的包装函数。
【解决方案3】:

为了更方便使用,您可以创建一个新对象专门指定一个位置参数,当顺序列出要冻结的位置参数的值时要跳过该位置参数partial

SKIP = object()

def partial_positionals(func, *positionals, **keywords):
    def wrapper(*args, **kwargs):
        arg = iter(args)
        return func(*(*(next(arg) if i is SKIP else i for i in positionals), *arg),
            **{**keywords, **kwargs})
    return wrapper

这样:

def f(x, y, z):
    return x + 2 * y + 3 * z

print(partial_positionals(f, 4, SKIP, 6)(5))

输出:

32

【讨论】:

    猜你喜欢
    • 2014-02-02
    • 1970-01-01
    • 1970-01-01
    • 2013-08-16
    • 2012-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-07
    相关资源
    最近更新 更多