【问题标题】:Can one partially apply the second argument of a function that takes no keyword arguments?可以部分应用不带关键字参数的函数的第二个参数吗?
【发布时间】:2012-06-25 18:47:54
【问题描述】:

以python内置pow()函数为例。

xs = [1,2,3,4,5,6,7,8]

from functools import partial

list(map(partial(pow,2),xs))

>>> [2, 4, 8, 16, 32, 128, 256]

但是我如何将 xs 提高到 2 的幂呢?

获取[1, 4, 9, 16, 25, 49, 64]

list(map(partial(pow,y=2),xs))

TypeError: pow() takes no keyword arguments

我知道列表推导会更容易。

【问题讨论】:

  • 另一种从第二个参数开始的部分用法是部分方法省略了自己的参数
  • 你可以使用的方法:def meth(cls, self,...) 然后partial(meth, cls)

标签: python arguments partial-application


【解决方案1】:

没有

根据the documentationpartial 不能这样做(强调我自己的):

partial.args

最左边的位置参数将被添加到位置参数


您总是可以“修复”pow 以使用关键字 args:

_pow = pow
pow = lambda x, y: _pow(x, y)

【讨论】:

  • 如果在 python 4.X pow() 中是“固定的”,我们都会知道他们的想法是从哪里得到的:)
  • @beoliver:实际上,截至PEP-457,仅位置参数被形式化为似乎是一个功能,所以我永远不会期望修复pow
  • 在这里使用 lambda 时,您不再需要 functools.partial()
  • 这是在做什么?论点根本没有改变:我本来预计 x,y -> y,x 的顺序会颠倒
  • 跟进我的评论:lambda x,y_pow(x,y) 中的一个不应该将x,y 颠倒过来吗??
【解决方案2】:

我想我会使用这个简单的单行:

import itertools
print list(itertools.imap(pow, [1, 2, 3], itertools.repeat(2)))

更新:

我还想出了一个比有用的更有趣的解决方案。这是一个漂亮的语法糖,受益于... 在Python3 中的字面意思是Ellipsis。它是partial 的修改版本,允许省略最左边和最右边之间的一些位置参数。唯一的缺点是您不能再将省略号作为参数传递。

import itertools
def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*(newfunc.leftmost_args + fargs + newfunc.rightmost_args), **newkeywords)
    newfunc.func = func
    args = iter(args)
    newfunc.leftmost_args = tuple(itertools.takewhile(lambda v: v != Ellipsis, args))
    newfunc.rightmost_args = tuple(args)
    newfunc.keywords = keywords
    return newfunc

>>> print partial(pow, ..., 2, 3)(5) # (5^2)%3
1
>>> print partial(pow, 2, ..., 3)(5) # (2^5)%3
2
>>> print partial(pow, 2, 3, ...)(5) # (2^3)%5
3
>>> print partial(pow, 2, 3)(5) # (2^3)%5
3

所以原始问题的解决方案是使用这个版本的部分list(map(partial(pow, ..., 2),xs))

【讨论】:

  • 很好,出于某种原因,我从不使用重复。当我在 3.X 上时,它只是 list(map(pow, [1, 2, 3], itertools.repeat(2)))
  • 很好,我不知道他们在 Python3 中更改了map
  • 有趣的是非常聪明,有点可怕:P(另外,你不应该一开始就传递省略号,所以这不是什么缺点。除非你数“将省略号用于不应该使用的东西”是一个缺点。)
  • 在学习了一些函数式编程之后,对我来说这看起来像是python中一种奇怪的函数柯里化方式
  • @kosii 就是这样。令人印象深刻的是,Python 足够灵活,可以促进许多这些功能模式。
【解决方案3】:

为什么不创建一个快速的 lambda 函数来重新排序参数和部分参数

partial(lambda p, x: pow(x, p), 2)

【讨论】:

  • 我刚刚发现该方法在定义属性时很有用(直接使用 property() 函数,而不是作为装饰器),我需要将第二个参数默认为 __setitem__() 调用。跨度>
  • 那个时候,为什么偏呢?为什么不 f = lambda p, x=2: pow(x, p)。甚至 f = lambda p: pow(2, p)
【解决方案4】:

您可以为此创建一个辅助函数:

from functools import wraps
def foo(a, b, c, d, e):
    print('foo(a={}, b={}, c={}, d={}, e={})'.format(a, b, c, d, e))

def partial_at(func, index, value):
    @wraps(func)
    def result(*rest, **kwargs):
        args = []
        args.extend(rest[:index])
        args.append(value)
        args.extend(rest[index:])
        return func(*args, **kwargs)
    return result

if __name__ == '__main__':
    bar = partial_at(foo, 2, 'C')
    bar('A', 'B', 'D', 'E') 
    # Prints: foo(a=A, b=B, c=C, d=D, e=E)

免责声明:我没有使用关键字参数对此进行测试,因此它可能会因为它们而爆炸。另外我不确定这是否是 @wraps 的用途,但它似乎是正确的 -ish。

【讨论】:

  • 主要+1。我很想选择这个答案,但我想我更多的是考虑内置的functools.partial。但这绝对是得救了。我喜欢:)
【解决方案5】:

你可以使用闭包

xs = [1,2,3,4,5,6,7,8]

def closure(method, param):
  def t(x):
    return method(x, param)
  return t

f = closure(pow, 2)
f(10)
f = closure(pow, 3)
f(10)

【讨论】:

    【解决方案6】:

    你可以用lambda做到这一点,它比functools.partial()更灵活:

    pow_two = lambda base: pow(base, 2)
    print(pow_two(3))  # 9
    

    更笼统地说:

    def bind_skip_first(func, *args, **kwargs):
      return lambda first: func(first, *args, **kwargs)
    
    pow_two = bind_skip_first(pow, 2)
    print(pow_two(3))  # 9
    

    lambda 的一个缺点是某些库无法序列化它。

    【讨论】:

    • 有些库是什么意思?我认为没有库能够序列化它们。
    • 我经常使用 ruamel.yaml,它可以很好地序列化 lambda。
    • 好的,我在考虑内置模块。感谢您的澄清:)
    【解决方案7】:

    一种方法是:

    def testfunc1(xs):
        from functools import partial
        def mypow(x,y): return x ** y
        return list(map(partial(mypow,y=2),xs))
    

    但这涉及重新定义 pow 函数。

    如果不需要使用 partial ,那么一个简单的 lambda 就可以解决问题

    def testfunc2(xs):
        return list(map(lambda x: pow(x,2), xs))
    

    映射 2 的 pow 的具体方法是

    def testfunc5(xs):
        from operator import mul
        return list(map(mul,xs,xs))
    

    但这些都没有直接解决与关键字参数相关的部分应用问题

    【讨论】:

    • 这应该添加到问题中,而不是作为答案发布。
    • 昨天有人建议我添加我的尝试作为答案而不是问题。这是一个没有胜利的情况!
    • 我觉得这里没问题。您应该将其更多地表述为“这是一种方法”,而不是作为问题的补充。在某种程度上,假装你是其他人在回答。
    • @Eric,刚刚读了一遍。我认为这个答案是一致的。我会稍微调整一下
    【解决方案8】:

    如前所述,如果您想要 partial 的函数不接受关键字参数,则这是 functools.partial 的限制。

    如果您不介意使用外部库 1,您可以使用 iteration_utilities.partial,它有一个支持占位符的部分:

    >>> from iteration_utilities import partial
    >>> square = partial(pow, partial._, 2)  # the partial._ attribute represents a placeholder
    >>> list(map(square, xs))
    [1, 4, 9, 16, 25, 36, 49, 64]
    

    1 免责声明:我是iteration_utilities 库的作者(如果您有兴趣,可以找到安装说明in the documentation)。

    【讨论】:

      【解决方案9】:

      非常通用的funcy 包含一个rpartial 函数,可以准确解决这个问题。

      xs = [1,2,3,4,5,6,7,8]
      from funcy import rpartial
      list(map(rpartial(pow, 2), xs))
      # [1, 4, 9, 16, 25, 36, 49, 64]
      

      这只是引擎盖下的 lambda:

      def rpartial(func, *args):
          """Partially applies last arguments."""
          return lambda *a: func(*(a + args))
      

      【讨论】:

      • 不是“完全”。第二个参数不一定是最后一个。
      【解决方案10】:

      即使这个问题已经得到解答,您也可以使用来自itertools.repeat 的配方获得您正在寻找的结果:

      from itertools import repeat
      
      
      xs = list(range(1, 9))  # [1, 2, 3, 4, 5, 6, 7, 8]
      xs_pow_2 = list(map(pow, xs, repeat(2)))  # [1, 4, 9, 16, 25, 36, 49, 64]
      

      希望这对某人有所帮助。

      【讨论】:

        【解决方案11】:

        如果你不能使用 lambda 函数,你也可以编写一个简单的包装函数来重新排序参数。

        def _pow(y, x):
            return pow(x, y)
        

        然后调用

        list(map(partial(_pow,2),xs))
        
        >>> [1, 4, 9, 16, 25, 36, 49, 64]
        

        【讨论】:

          【解决方案12】:

          是的

          如果您创建了部分类

          class MyPartial:
              def __init__(self, func, *args):
                  self._func = func
                  self._args = args
                  
              def __call__(self, *args):
                  return self._func(*args, *self._args) # swap ordering
              
          xs = [1,2,3,4,5,6,7,8]
          list(map(MyPartial(pow,2),xs))
          
          >>> [1, 4, 9, 16, 25, 36, 49, 64]
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2019-01-14
            • 1970-01-01
            • 2022-06-27
            • 1970-01-01
            • 2020-01-12
            • 1970-01-01
            • 2020-02-09
            • 1970-01-01
            相关资源
            最近更新 更多