能否部分应用不接受关键字参数的函数的第二个参数?

69

以 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

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


1
另一种使用 partial 的方式是从第二个参数开始,对省略 self 参数的方法进行 partial。 - Sławomir Lenart
你可以使用以下方法来编写程序:def meth(cls, self,...),然后使用 partial(meth, cls) - nadapez
13个回答

70

No

根据文档partial 无法实现此功能(强调为本人所加):

partial.args

将要预先添加到位置参数的最左边位置参数


你可以简单地“修复”pow以使用关键字参数:

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

1
如果在Python 4.X中pow()函数被“固定”,我们都会知道他们从哪里得到了灵感 :) - beoliver
6
在这里使用lambda时,您不再需要functools.partial() - danijar
这是在做什么?参数根本没有改变:我本来预期会将x,y的顺序颠倒-> y,x - WestCoastProjects
跟进我的评论:lambda x,y_pow(x,y) 中的 x,y 不是应该颠倒吗? - WestCoastProjects
@javadba:不,那行代码的上下文是“将pow修复为具有关键字参数”。一旦你这样做了,问题就不再相关了,因为pow不再是“一个不带关键字参数的函数”。如果在上面加上这个“修复”,那么OP的代码就可以正常工作。 - Eric
显示剩余2条评论

21
为什么不创建一个快速的lambda函数来重新排序参数并进行部分应用呢?
partial(lambda p, x: pow(x, p), 2)

1
我刚刚发现在定义属性时(直接使用property()函数,而不是作为装饰器),这种方法非常有用,因为我需要将第二个参数默认为__setitem__()调用。 - Eric Smith
7
为什么要将它部分化?为什么不这样写:f = lambda p, x=2: pow(x, p)。或者甚至可以这样写:f = lambda p: pow(2, p)。 - jobermark

20

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

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

更新:

我还想到了一个更有趣但没什么用的解决方案。它是一种美丽的语法糖,利用Python3中...字面量表示省略号的特性。这是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

因此,对于原问题的解决方案将是使用这个版本的partial:list(map(partial(pow, ..., 2),xs))


2
很好,出于某种原因我从来不使用repeat。而且由于我正在使用3.X版本,所以只需使用list(map(pow, [1, 2, 3], itertools.repeat(2)))即可。 - beoliver
不错,我不知道他们在Python3中改变了map - mutantacule
1
这个有趣的东西在某种程度上相当恐怖,但还是相当聪明的 :P (另外,你本来就不应该传递省略号,所以这并不是什么劣势。除非你认为“将省略号用于其不应用于的地方”是一个劣势。) - millimoose
2
在学习了一些函数式编程之后,对我来说,在Python中进行函数柯里化看起来有点奇怪。 - mutantacule
@kosii 这正是它的特点。令人印象深刻的是,Python足够灵活,可以促进许多这些函数式模式的实现。 - josiah

8
你可以为此创建一个辅助函数:
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的正确方法,但它看起来差不多。


重点加一。我很想选这个答案,但我想更多地考虑内置的functools.partial。不过这肯定会被保存。我喜欢 :) - beoliver

6

您可以使用闭包。

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)

4
您可以使用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的一个缺点是某些库无法序列化它。


你说的“一些库”是什么意思?我认为没有任何库能够将它们序列化。 - MSeifert
我经常使用ruamel.yaml,它可以很好地序列化lambda函数。 - danijar
好的,我在考虑内置模块。谢谢澄清 :) - MSeifert

2
一种做法是:
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的幂映射为特定的方法是:

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

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

2
尽管这个问题已经得到了回答,但你可以使用从 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]

希望这能帮到一些人。

def _remove_table_id(ds: List[Dict[str, Any]]) -> List[Dict[str, Any]]: return list(map(dict.pop, ds, repeat('table_id')))将列表中字典的键名为'table_id'的键值对删除后返回新的列表。 - amohr

2

是的,只要函数接受关键字参数,您就可以这样做。您只需要知道名称即可。

在使用 pow() 函数时(前提是您正在使用 Python 3.8 或更高版本),您需要使用 exp 而不是 y。

尝试执行以下操作:

xs = [1,2,3,4,5,6,7,8]
print(list(map(partial(pow,exp=2),xs)))

1
非常多才多艺的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))

1
第二个参数不一定要放在最后一个位置。 - Nuno André

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接