¿Cómo puedo simular el desempaquetado de tuplas de 2.x para parámetros lambda, usando 3.x?

7 minutos de lectura

avatar de usuario de balki
Balki

En Python 2, puedo escribir:

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

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

Pero eso no es compatible con 3.x:

  File "<stdin>", line 1
    min(points, key=lambda (x, y): (x*x + y*y))
                           ^
SyntaxError: invalid syntax

La solución sencilla es indexar explícitamente en la tupla que se pasó:

>>> 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

Pero porque el lambda solo admite una sola expresión, no es posible poner el x, y = p parte en ella.

¿De qué otra manera puedo evitar esta limitación?

  • Probablemente sea más probable que lambda para eliminarlo por completo del idioma y luego revertir los cambios que dificultaron su uso, pero puede intentar publicar en python-ideas si desea expresar su deseo de que se vuelva a agregar la función.

    – Woble

    19 de febrero de 2014 a las 21:42

  • Yo tampoco lo entiendo, pero parece que la BDFL se opone. lambda en el mismo espíritu que se opone map, reduce y filter.

    – Cuadue

    19 de febrero de 2014 a las 21:59

  • lambda estaba programado para su eliminación en py3k, ya que es básicamente una plaga en el lenguaje. Pero nadie pudo ponerse de acuerdo sobre una alternativa adecuada para definir funciones anónimas, por lo que finalmente Guido alzó los brazos en señal de derrota y eso fue eso.

    – Roippi

    19 de febrero de 2014 a las 22:05

  • Las funciones anónimas son imprescindibles en cualquier lenguaje adecuado, y me gustan bastante las lambdas. Tendré que leer los porqués de semejante debate. (Además, aunque map y filter se reemplazan mejor por comprensiones, me gusta reduce)

    – njzk2

    20 de febrero de 2014 a las 18:39


  • Lo único que no me gusta de Python 3…

    – timgeb

    23 de diciembre de 2016 a las 9:52

avatar de usuario de jsbueno
jsbueno

No, no hay otra forma. Lo cubriste todo. El camino a seguir sería plantear esta cuestión en el Lista de correo de ideas de Pythonpero prepárate para discutir mucho allí para ganar algo de tracción.

En realidad, solo para no decir “no hay salida”, una tercera forma podría ser implementar un nivel más de llamadas 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))

Actualización de Python 3.8

Desde el lanzamiento de Python 3.8, PEP 572 (expresiones de asignación) ha estado disponible como herramienta.

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, es posible hacer lo siguiente:

>>> 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 frases ingeniosas que operarían en este pointpodría valer la pena tener un namedtupley use la expresión de asignación para “convertir” 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]

  • Y, por supuesto, olvidé mencionar que la forma “única y obvia” de hacer esto es gastar la función de tres líneas usando def que se menciona en la pregunta bajo el nombre some_name_to_think-of.

    – jsbueno

    10 de mayo de 2016 a las 3:26

  • 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

    7 de noviembre de 2018 a las 15:07

  • expresiones de asignacion!! Estoy (gratamente) sorprendido de que lo hayan logrado en

    – Proyectos de la costa oeste

    3 de junio de 2019 a las 22:17

  • Disfruté esa forma de obtener el operador de coma de C. 😀 [a, b, c][-1]

    – A la derecha

    26 de agosto de 2022 a las 10:14

  • Entonces… no podemos hacer algo como x,y := p ¿todavía?

    – SnzPor16Min

    20 de enero a las 8:18

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

Dado que las lambdas utilizan parámetros de tupla debido a la limitación de expresión única, también deben admitirse. Esto se hace vinculando el argumento de la secuencia esperada 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.

  • Que bueno que no se quita en for loop o comprensiones

    – balki

    20 de febrero de 2014 a las 18:32

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

  • si no puede pensar en un nombre; utilice el nombre del parámetro de la 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ías ver si un 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: p.x**2 + p.y**3)
    

  • Entre todas las respuestas a 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 como la diferencia entre lambda (x, y): y lambda x, y: no es evidente a primera vista.

    – Burak Arslán

    9 mayo 2018 a las 14:45


Avatar de usuario de Rahul Gopinath
Raúl Gopinath

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.

Por ejemplo:

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

En comparación con la respuesta aceptada de usar un contenedor, esta solución puede desestructurar completamente los argumentos mientras que el contenedor solo desestructura el primer nivel. Es decir, puedes hacer

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

En comparación con la respuesta aceptada usando un unwrapper lambda:

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 usar una lista en lugar de una tupla.

values = [(('A',1),'a'), (('B',0),'b')]
print(min(points, 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. Sin embargo, en mi opinión, esto es mejor que el truco de usar múltiples expresiones en una tupla y devolver la última.

Creo que la mejor sintaxis es x * x + y * y let x, y = point, let la palabra clave debe elegirse con más cuidado.

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

El asistente de función de alto orden sería útil en caso de que le demos un nombre propio.

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

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

Considere si necesita desempaquetar 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 desempaquetar:

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

avatar de usuario de njzk2
njzk2

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)]

¿Ha sido útil esta solución?