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

95

Considere el siguiente código de 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 es compatible con 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, 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 lo 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 función debería volver a agregarse al menos solo a las lambdas. ¿Existe 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 parastar

import functools

def star(f):
    @functools.wraps(f):
    def f_inner(args):
        return f(*args)
    return f_inner
balki
fuente
4
Probablemente sea más probable lambdaque se elimine del lenguaje por completo que revertir los cambios que lo hicieron más difícil de usar, pero podría intentar publicar en Python-ideas si desea expresar su deseo de que se vuelva a agregar la función.
Wooble
3
Yo tampoco lo entiendo, pero parece que el BDFL se opone lambdacon el mismo espíritu que se opone map, reducey filter.
Cuadue
3
lambdaestaba programado para su eliminación en py3k, ya que básicamente es una plaga para el lenguaje. Pero nadie pudo ponerse de acuerdo sobre una alternativa adecuada para definir funciones anónimas, por lo que eventualmente Guido levantó los brazos en señal de derrota y eso fue todo.
roippi
5
Las funciones anónimas son imprescindibles en cualquier idioma adecuado, y me gustan bastante las lambdas. Tendré que leer los porqués de tal debate. (Además, aunque mapy filterse reemplazan mejor por comprensiones, me gusta reduce)
njzk2
13
Lo único que no me gusta de Python 3 ...
timgeb

Respuestas:

33

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 prepárese para discutir mucho allí para ganar algo de tracción.

En realidad, para no decir "no hay salida", una tercera forma podría ser implementar un nivel más de llamada 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 una lambda, generalmente lo hago creando una tupla y simplemente devolviendo el último componente de la misma, es posible hacer:

>>> 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 varias pointfrases ingeniosas que operarían en esto , podría valer la pena tener una tupla con nombre y usar la expresión de asignación para "lanzar" efectivamente la secuencia entrante a la tupla con nombre:

>>> from collections import namedtuple
>>> point = namedtuple("point", "x y")
>>> b = lambda s: (p:=point(*s), p.x ** 2 + p.y ** 2)[-1]
jsbueno
fuente
3
Y, por supuesto, olvidé mencionar que la forma "única y obvia" de hacer esto es utilizar la función de tres líneas defque se menciona en la pregunta debajo del nombre some_name_to_think-of.
jsbueno
2
Esta respuesta todavía ve algunos visitantes: con la implementación de PEP 572, debería haber una forma de crear variables dentro de la expresión lambda, como en:key = lambda p: (x:=p[0], y:=p[1], x ** 2 + y ** 2)[-1]
jsbueno
1
expresiones de asignación !! Estoy (gratamente) sorprendido de que hayan llegado
javadba
24

Según http://www.python.org/dev/peps/pep-3113/, el desempaquetado de tuplas se ha ido y 2to3los traducirá así:

Dado que los parámetros de tupla son utilizados por lambdas debido a la limitación de expresión única, también deben ser compatibles. Esto se hace enlazando el argumento de secuencia esperado a un solo parámetro y luego indexando ese parámetro:

lambda (x, y): x + y

se traducirá en:

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

Lo cual es bastante similar a su implementación.

njzk2
fuente
3
Es bueno que no se elimine en bucle for ni comprensiones
balki
12

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

  • si no puede 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)
  • podría ver si namedtuplehace 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: p.x**2 + p.y**3)
jfs
fuente
Entre todas las respuestas para esta pregunta hasta ahora, usar una tupla con nombre es el mejor curso de acción aquí. No solo puede mantener su lambda, sino que también encuentro que la versión namedtuple es más legible ya que la diferencia entre lambda (x, y):y lambda x, y:no es obvia a primera vista.
Burak Arslan
5

Si bien 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 darle la vuelta a las funciones, y el rendimiento no es una declaración y, por lo tanto, está permitido dentro de 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 contenedor, esta solución es capaz de desestructurar completamente los argumentos mientras que el contenedor solo desestructura 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.

rahul
fuente
0

Según la sugerencia de Cuadue y su comentario sobre cómo desempacar 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)]
njzk2
fuente
0

Otra opción es escribirlo en un generador que produzca una tupla donde la clave es el primer elemento. Las tuplas se comparan desde el principio hasta el final, por lo que se devuelve la tupla con el primer elemento más pequeño. Luego puede indexar en el resultado para obtener el valor.

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

Considere si necesita descomprimir la tupla en primer lugar:

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

o si necesita proporcionar nombres explícitos al desembalar:

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

Creo que la mejor es la sintaxis x * x + y * y let x, y = point,let la palabra clave debe ser más cuidadosamente elegido.

La doble lambda es la versión más cercana. lambda point: (lambda x, y: x * x + y * y)(*point)

El ayudante de función de orden superior sería útil en caso de que le demos un nombre adecuado.

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

destruct_tuple(lambda x, y: x * x + y * y)
anthony.hl
fuente