¿Cuál es el buen equivalente de python3 para el desempaquetado automático de tuplas en lambda?

Considere el siguiente código python2

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

Esto no está soportado en python3 y tengo que hacer lo siguiente:

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

Esto es muy feo. Si la lambda fuera una función, yo podría hacer

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

La eliminación de esta función en python3 obliga al código a hacer el camino feo (con índices mágicos) o crear funciones innecesarias (la parte más molesta es pensar en buenos nombres para estas funciones innecesarias)

Creo que la característica debería agregarse al menos solo a las lambdas. ¿Hay una buena alternativa?


Actualización: estoy usando el siguiente ayudante extendiendo la idea en la respuesta

 def star(f): return lambda args: f(*args) min(points, key=star(lambda x,y: (x*x + y*y)) 

Update2: Una versión más limpia para la star

 import functools def star(f): @functools.wraps(f): def f_inner(args): return f(*args) return f_inner 

No, no hay otra manera. Lo cubriste todo. El camino a seguir sería plantear este problema en la lista de correo de ideas de Python , pero estar preparado para discutir mucho allí para ganar algo de tracción.

En realidad, solo para no decir “no hay salida”, una tercera manera podría ser implementar un nivel más de llamadas a lambda solo para desplegar los parámetros, pero eso sería a la vez más ineficiente y más difícil de leer que sus dos sugerencias:

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

actualizar Python 3.8

A partir de ahora, Python 3.8 alpha1 está disponible, y se implementan las expresiones de asignación PEP 572.

Entonces, si uno usa un truco para ejecutar múltiples expresiones dentro de un lambda, generalmente lo hago creando una tupla y simplemente devolviendo el último componente de la misma, es posible hacerlo:

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

Se debe tener en cuenta que este tipo de código rara vez será más legible o práctico que tener una función completa. Aún así, hay usos posibles: si hay varios de una sola línea que funcionarán en este point , podría valer la pena tener un grupo con nombre, y usar la expresión de asignación para “convertir” efectivamente la secuencia entrante al grupo con nombre:

 >>> from collections import namedtuple >>> point = namedtuple("point", "xy") >>> b = lambda s: (p:=point(*s), px ** 2 + py ** 2)[-1] 

De acuerdo con http://www.python.org/dev/peps/pep-3113/ el desempaquetado de la tupla se ha ido, y 2to3 los traducirá así:

Como los parámetros de tupla son utilizados por lambdas debido a la limitación de una sola expresión, también deben ser compatibles. Esto se hace teniendo el argumento de la secuencia esperada vinculado a un solo parámetro y luego indexando ese parámetro:

lambda (x, y): x + y

será traducido a:

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

Lo cual es bastante similar a tu implementación.

No conozco ninguna buena alternativa general al comportamiento de desempaquetado de los argumentos de Python 2. Aquí hay un par de sugerencias que podrían ser útiles en algunos casos:

  • si no puedes pensar en un nombre; use el nombre del parámetro de palabra clave:

     def key(p): # more specific name would be better x, y = p return x**2 + y**3 result = min(points, key=key) 
  • puede ver si un grupo con namedtuple hace que su código sea más legible si la lista se usa en varios lugares:

     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: px**2 + py**3) 

Mientras que los argumentos de desestructuración se eliminaron en Python3, no se eliminaron de las comprensiones. Es posible abusar de él para obtener un comportamiento similar en Python 3. En esencia, aprovechamos el hecho de que las co-rutinas nos permiten convertir las funciones al revés, y el rendimiento no es una statement, y por lo tanto está permitido dentro de las lambdas.

Por ejemplo:

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

En comparación con la respuesta aceptada de usar un envoltorio, esta solución puede destruir completamente los argumentos, mientras que el envoltorio solo destruye el primer nivel. Es decir,

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

En comparación con

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

Alternativamente también se puede hacer.

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

O un poco mejor

 print(min(values, key=lambda y: next(b for ((a,b),c) in (y,)))) 

Esto es solo para sugerir que se puede hacer, y no debe tomarse como una recomendación.

Basado en la sugerencia de Cuadue y su comentario sobre el desempaquetado que aún está presente en las comprensiones, puede usar, usando numpy.argmin :

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

Otra opción es escribirlo en un generador que produzca una tupla en la que la clave sea el primer elemento. Las tuplas se comparan de principio a fin, por lo que se devuelve la tupla con el primer elemento más pequeño. A continuación, puede indexar en el resultado para obtener el valor.

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