在 lambda 中,对于自动元组解包,Python 3的优秀等价物是什么?

考虑下面的 python2代码

In [5]: points = [ (1,2), (2,3)]


In [6]: min(points, key=lambda (x, y): (x*x + y*y))
Out[6]: (1, 2)

Python3不支持这一点,我必须执行以下操作:

>>> min(points, key=lambda p: p[0]*p[0] + p[1]*p[1])
(1, 2)

这太丑了,如果 Lambda 是一个函数,我也可以

def some_name_to_think_of(p):
x, y = p
return x*x + y*y

在 python3中删除这个特性,会迫使代码要么采用丑陋的方式(使用神奇的索引) ,要么创建不必要的函数(最麻烦的部分是为这些不必要的函数想一个好名字)

我认为这个功能至少应该单独添加到 lambdas 中。有没有一个好的替代方案?


更新: 我正在使用下面的助手扩展答案中的想法

def star(f):
return lambda args: f(*args)


min(points, key=star(lambda x,y: (x*x + y*y))

Update2: star的更清晰版本

import functools


def star(f):
@functools.wraps(f)
def f_inner(args):
return f(*args)
return f_inner
44889 次浏览

根据 http://www.python.org/dev/peps/pep-3113/的说法,元组解包已经不存在了,2to3会这样翻译它们:

因为 lambdas 使用了单元组参数 表达式限制,它们也必须得到支持 将期望的序列参数绑定到单个参数并且 然后对该参数进行索引:

lambda (x, y): x + y

将被翻译成:

lambda x_y: x_y[0] + x_y[1]

这和你的实现非常相似。

不,没有别的办法了。你都说了。解决的办法是在 Python 创意邮件列表上提出这个问题,但要准备好在那里进行大量争论,以获得一些牵引力。

实际上,只是不要说“没有出路”,第三种方法可能是实现一个更高级别的 lambda 调用,只是为了展开参数——但是这将同时比你的两个建议更加低效和难以阅读:

min(points, key=lambda p: (lambda x,y: (x*x + y*y))(*p))

Python 3.8更新

自从 Python 3.8发布以来,PEP 572ーー赋值表达式ーー已经成为可用的工具。

因此,如果在 lambda 中使用一个技巧来执行多个表达式——我通常通过创建一个 tuple 并返回它的最后一个组件来做到这一点,那么可以执行以下操作:

>>> a = lambda p:(x:=p[0], y:=p[1], x ** 2 + y ** 2)[-1]
>>> a((3,4))
25

人们应该记住,这种代码很少比拥有一个完整的函数更具可读性或实用性。尽管如此,还是有一些可能的用法——如果有各种一行程序可以操作这个 point,那么值得使用一个 namedtuple,并使用赋值表达式有效地将传入序列“强制转换”为 namedtuple:

>>> from collections import namedtuple
>>> point = namedtuple("point", "x y")
>>> b = lambda s: (p:=point(*s), p.x ** 2 + p.y ** 2)[-1]

对于 Python2参数的解包行为,我不知道任何好的通用替代方法。下面是一些在某些情况下可能有用的建议:

  • 如果你想不出一个名字; 使用关键字参数的名字:

    def key(p): # more specific name would be better
    x, y = p
    return x**2 + y**3
    
    
    result = min(points, key=key)
    
  • you could see if a namedtuple makes your code more readable if the list is used in multiple places:

    from collections import namedtuple
    from itertools import starmap
    
    
    points = [ (1,2), (2,3)]
    Point = namedtuple('Point', 'x y')
    points = list(starmap(Point, points))
    
    
    result = min(points, key=lambda p: p.x**2 + p.y**3)
    

根据 Cuadue 的建议和您关于拆包的意见仍然存在于理解中,您可以使用 numpy.argmin:

result = points[numpy.argmin(x*x + y*y for x, y in points)]

虽然在 Python 3中删除了解构参数,但是没有从理解中删除它。在 Python3中,可以滥用它来获得类似的行为。

例如:

points = [(1,2), (2,3)]
print(min(points, key=lambda y: next(x*x + y*y for (x,y) in [y])))

与使用包装器的公认答案相比,这种解决方案能够完全解构参数,而包装器只解构第一级。也就是说,你可以做到

values = [(('A',1),'a'), (('B',0),'b')]
print(min(values, key=lambda y: next(b for ((a,b),c) in (y,))))

与使用解包装 lambda 的公认答案相比:

values = [(('A',1),'a'), (('B',0),'b')]
print(min(points, key=lambda p: (lambda a,b: (lambda x,y: (y))(*a))(*p)))

或者,也可以使用列表来代替元组。

values = [(('A',1),'a'), (('B',0),'b')]
print(min(points, key=lambda y: next(b for (a,b),c in [y])))

这只是建议可以这样做,而不应该被当作一个建议。但是,IMO,这比在元组中使用多个表达式并返回最后一个表达式要好。

另一种选择是将它写入生成元组的生成器,其中键是第一个元素。元组从头到尾进行比较,因此返回第一个元素最小的元组。然后可以对结果进行索引以获取值。

min((x * x + y * y, (x, y)) for x, y in points)[1]

首先考虑是否需要解压元组:

min(points, key=lambda p: sum(x**2 for x in p))

或者在解包时是否需要提供明确的名称:

min(points, key=lambda p: abs(complex(*p))

我认为比较好的语法是 x * x + y * y let x, y = pointlet关键字应该更仔细地选择。

双 lambda 是最接近的版本。 lambda point: (lambda x, y: x * x + y * y)(*point)

如果我们给它一个合适的名称,高阶函数 helper 将非常有用。

def destruct_tuple(f):
return lambda args: f(*args)


destruct_tuple(lambda x, y: x * x + y * y)

是一个真正的解决方案,使用 自我功能

虽然目前还不支持,但我已经提交了一个 元组参数解包特性请求来支持:

(
seq((1, 2), (3, 4))
.map(unpack=lambda a, b: a + b)
)  # => [3, 7]