如何使用 3.x 模拟 2.x 对 lambda 参数的元组解包?
- 2025-02-27 09:06:00
- admin 原创
- 56
问题描述:
在 Python 2 中,我可以写:
In [5]: points = [ (1,2), (2,3)]
In [6]: min(points, key=lambda (x, y): (x*x + y*y))
Out[6]: (1, 2)
但是 3.x 版本不支持此功能:
File "<stdin>", line 1
min(points, key=lambda (x, y): (x*x + y*y))
^
SyntaxError: invalid syntax
直接的解决方法是对传递的元组进行明确索引:
>>> 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
但是由于lambda
仅支持单个表达式,因此无法将x, y = p
部分放入其中。
我还能怎样解决这个限制?
解决方案 1:
不,没有其他办法。您已经全部解决了。解决方法是在Python ideas 邮件列表上提出这个问题,但要做好在那里争论不休的准备,以获得一些支持。
实际上,不仅仅是说“没有出路”,第三种方法可能是实现另一个级别的 lambda 调用以展开参数 - 但这比你的两个建议更有效率且更难阅读:
min(points, key=lambda p: (lambda x,y: (x*x + y*y))(*p))
Python 3.8 更新
自 Python 3.8 发布以来,PEP 572(赋值表达式)已作为一种工具使用。
因此,如果使用技巧在 lambda 中执行多个表达式 - 我通常通过创建一个元组并仅返回它的最后一个组件来实现,则可以执行以下操作:
>>> a = lambda p:(x:=p[0], y:=p[1], x ** 2 + y ** 2)[-1]
>>> a((3,4))
25
应该记住,这种代码很少会比具有完整函数的代码更易读或更实用。不过,还是有用途的 - 如果有各种可以对此进行操作的单行代码point
,则值得拥有一个namedtuple
,并使用赋值表达式有效地将传入序列“投射”到命名元组:
>>> from collections import namedtuple
>>> point = namedtuple("point", "x y")
>>> b = lambda s: (p:=point(*s), p.x ** 2 + p.y ** 2)[-1]
解决方案 2:
根据http://www.python.org/dev/peps/pep-3113/ ,元组解包已经消失,2to3
并将像这样翻译它们:
由于单表达式限制,lambda 使用了元组参数,因此也必须支持它们。这是通过将预期的序列参数绑定到单个参数,然后对该参数进行索引来实现的:
lambda (x, y): x + y
将被翻译成:
lambda x_y: x_y[0] + x_y[1]
这与您的实现非常相似。
解决方案 3:
我不知道有什么好的通用方法可以替代 Python 2 参数解包行为。以下是一些在某些情况下可能有用的建议:
如果您想不出名字;请使用关键字参数的名称:
def key(p): # more specific name would be better
x, y = p
return x**2 + y**3
result = min(points, key=key)
你可以看看
namedtuple
如果在多个地方使用列表,你的代码是否会更具可读性:
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)
解决方案 4:
虽然解构参数在 Python3 中被移除了,但它并没有从推导式中移除。在 Python 3 中,可以滥用它来获得类似的行为。
例如:
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])))
这只是建议这样做,不应视为建议。但是,在我看来,这比在元组中使用多个表达式并返回最后一个表达式要好。
解决方案 5:
我认为语法越好x * x + y * y let x, y = point
,let
关键字的选择就越谨慎。
双 lambda 是最接近的版本。lambda point: (lambda x, y: x * x + y * y)(*point)
如果我们给高阶函数助手一个合适的名字,它就会很有用。
def destruct_tuple(f):
return lambda args: f(*args)
destruct_tuple(lambda x, y: x * x + y * y)
解决方案 6:
由于 Stack Overflow 上的问题不应该包含答案,也不应该有明确的“更新”部分,因此我将 OP 的原始“更新”转换为正确的答案并将其作为社区 wiki。
OP 最初声称这个解决方案是“扩展答案中的想法”。我无法辨别这意味着哪个答案或哪个想法。这个想法在功能上与anthony.hl 的答案相同,但那是几年后的事情了。考虑到当时答案的状态,我认为这符合 OP 的原创作品。)
创建一个包装函数来概括解包参数的过程,如下所示:
def star(f):
return lambda args: f(*args)
现在我们可以使用它来将我们想要编写的 lambda 转换为可以正确接收参数的 lambda:
min(points, key=star(lambda x,y: (x*x + y*y))
我们可以使用functools.wraps进一步清理它:
import functools
def star(f):
@functools.wraps(f)
def f_inner(args):
return f(*args)
return f_inner
解决方案 7:
首先考虑一下是否需要解包元组:
min(points, key=lambda p: sum(x**2 for x in p))
或者在解包时是否需要提供明确的名称:
min(points, key=lambda p: abs(complex(*p)**2)
解决方案 8:
根据 Cuadue 的建议和您对解包仍然存在于理解中的评论,您可以使用numpy.argmin
:
result = points[numpy.argmin(x*x + y*y for x, y in points)]
解决方案 9:
另一种选择是将其写入生成器,生成一个元组,其中键是第一个元素。从头到尾比较元组,因此返回具有最小第一个元素的元组。然后您可以索引结果以获取值。
min((x * x + y * y, (x, y)) for x, y in points)[1]
解决方案 10:
使用PyFunctional可能确实可以解决这个问题!
虽然目前不支持,但我已经提交了元组参数解包功能请求以支持:
(
seq((1, 2), (3, 4))
.map(unpack=lambda a, b: a + b)
) # => [3, 7]
扫码咨询,免费领取项目管理大礼包!