No Lambda Multilínea en Python: ¿Por qué no?

335

He oído decir que no se pueden agregar lambdas multilínea en Python porque chocarían sintácticamente con las otras construcciones de sintaxis en Python. Hoy estaba pensando en esto en el autobús y me di cuenta de que no podía pensar en una sola construcción de Python con la que las lambdas multilínea chocaran. Dado que conozco bastante bien el idioma, esto me sorprendió.

Ahora, estoy seguro de que Guido tenía una razón para no incluir lambdas multilínea en el idioma, pero por curiosidad: ¿cuál es la situación en la que incluir una lambda multilínea sería ambiguo? ¿Es cierto lo que he escuchado, o hay alguna otra razón por la que Python no permite lambdas multilínea?

Imaginista
fuente
12
Versión tl; dr: porque Python es un lenguaje perezoso sin bloques {} y, por lo tanto, esto no estaba permitido para mantener un diseño sintáctico consistente.
Andrew
11
Además: estoy completamente sorprendido de que nadie haya mencionado esto en las respuestas ... Puede finalizar líneas con el carácter \ en Python y continuar en la siguiente línea ... Esta información reemplaza un poco a toda esta pregunta, así que ...
Andrew
"diseño sintáctico"
nicolas
Esto requeriría permitir declaraciones dentro de expresiones. Si vas a hacer eso, no necesitas lambdaexpresiones en primer lugar; simplemente podría usar defdeclaraciones en expresiones.
chepner

Respuestas:

153

Mira lo siguiente:

map(multilambda x:
      y=x+1
      return y
   , [1,2,3])

¿Es esto un retorno de lambda (y, [1,2,3])(por lo tanto, el mapa solo obtiene un parámetro, lo que resulta en un error)? ¿O vuelve y? ¿O es un error de sintaxis, porque la coma en la nueva línea está fuera de lugar? ¿Cómo sabría Python lo que quieres?

Dentro de los parens, la sangría no es importante para Python, por lo que no puede trabajar sin ambigüedades con líneas múltiples.

Este es solo uno simple, probablemente haya más ejemplos.

balpha
fuente
107
podrían forzar el uso de paréntesis si desea devolver una tupla de una lambda. En mi opinión, esto siempre debería haberse aplicado para evitar tales ambigüedades, pero bueno.
mpen
26
Esta es una ambigüedad simple que debe resolverse agregando un conjunto adicional de elementos parentales, algo que ya existe en muchos lugares, por ejemplo, expresiones generadoras rodeadas de otros argumentos, llamando a un método en un entero literal (aunque esto no tiene por qué ser el caso ya que un el nombre de la función no puede comenzar con un dígito) y, por supuesto, con lambdas de una sola línea (que pueden ser expresiones largas escritas en varias líneas). Las lambdas de líneas múltiples no serían especialmente diferentes de estos casos, ya que justifica excluirlas sobre esa base. Esta es la verdadera respuesta.
nmclean
3
Me gusta cómo hay lenguas tropecientos que tienen que ver con ella sin trastes, pero de alguna manera hay algunas razones profundas por las que si no es muy difícil supuestamente imposible
Nicolas
1
@nicolas eso es Python en pocas palabras
javadba
Motivo por el que no uso lambda, tan poco desarrollado en Python.
NoName
635

Guido van Rossum (el inventor de Python) responde a esta pregunta exacta él mismo en una antigua publicación de blog .
Básicamente, admite que es teóricamente posible, pero que cualquier solución propuesta no sería Pythonic:

"Pero la complejidad de cualquier solución propuesta para este rompecabezas es inmensa, para mí: requiere que el analizador (o más precisamente, el lexer) pueda alternar entre modos sensibles a las sangrías e insensibles a las sangrías, manteniendo una pila de modos anteriores y nivel de sangría. Técnicamente todo eso puede resolverse (ya hay una pila de niveles de sangría que podrían generalizarse). Pero nada de eso me quita la sensación de que todo es un artilugio elaborado de Rube Goldberg ".

Eli Courtwright
fuente
108
¿Por qué no es esta la mejor respuesta? No se trata de razones técnicas, es una elección de diseño, como lo indica claramente el inventor.
Dan Abramov
13
@DanAbramov porque el OP no se conectó durante años probablemente.
contrato del Prof. Falken incumplió el
77
Para aquellos que no entendieron la referencia de Rube Goldberg, ver: en.wikipedia.org/wiki/Rube_Goldberg_Machine
fjsj
56
La respuesta de Guido es solo otra razón por la que desearía que Python no dependiera de la sangría para definir bloques.
LS
25
No estoy seguro de llamar a "instinto" una opción de diseño. ;)
Elliot Cameron
54

Esto generalmente es muy feo (pero a veces las alternativas son aún más feas), por lo que una solución alternativa es hacer una expresión de llaves:

lambda: (
    doFoo('abc'),
    doBar(123),
    doBaz())

Sin embargo, no aceptará ninguna tarea, por lo que deberá preparar los datos de antemano. El lugar donde encontré esto útil es el contenedor PySide, donde a veces tienes devoluciones de llamada cortas. Escribir funciones miembro adicionales sería aún más feo. Normalmente no necesitarás esto.

Ejemplo:

pushButtonShowDialog.clicked.connect(
    lambda: (
    field1.clear(),
    spinBox1.setValue(0),
    diag.show())
Sebastian Bartos
fuente
2
Mi jefe solo estaba pidiendo algo como esto en nuestra aplicación PyQt. ¡Increíble!
TheGerm
1
Gracias por esto, también estaba buscando una buena manera de usar lambdas cortas (pero aún multilínea) como devoluciones de llamada para nuestra interfaz de usuario PySide.
Michael Leonard
Y ahora que he visto esto, sugirió instantáneamente usar lambda argy setattr(arg, 'attr','value')subvertir "sin asignaciones ...". Y luego está la evaluación de cortocircuito de andy or... es el Javascript que lo hace. Te hunde las raíces, como la hiedra en la pared. Casi espero olvidar esto por Navidad.
nigel222
bastante inteligente, y bastante legible. Ahora - sobre esas tareas (faltantes ...) ...
javadba
@ nigel222 ¿por qué sentir vergüenza? El lenguaje python está fundamentalmente paralizado, pero es el que se usa de todos modos para gran parte de la ciencia de datos . Entonces hacemos ajustes. El lenguaje debe manejar bien las formas de hacer los efectos secundarios (¡con frecuencia, simplemente imprimiendo / registrando!) Y las tareas (¡con frecuencia lo suficiente para solo variables intermedias!). Pero ni siquiera son compatibles (al menos si sigues las PEPpautas)
javadba
17

Un par de enlaces relevantes:

Durante un tiempo, seguí el desarrollo de Reia, que inicialmente iba a tener una sintaxis basada en sangría de Python con bloques Ruby también, todo encima de Erlang. Pero, el diseñador terminó renunciando a la sensibilidad a la sangría, y esta publicación que escribió sobre esa decisión incluye una discusión sobre los problemas que encontró con la sangría + bloques de varias líneas, y una mayor apreciación que ganó por los problemas / decisiones de diseño de Guido:

http://www.unlimitednovelty.com/2009/03/indentation-sensitivity-post-mortem.html

Además, aquí hay una propuesta interesante para bloques de estilo Ruby en Python con los que me encontré, donde Guido publica una respuesta sin derribarla (aunque no estoy seguro de si ha habido un derribo posterior):

http://tav.espians.com/ruby-style-blocks-in-python.html

Luego
fuente
12

[Editar] Lee esta respuesta. Explica por qué la lambda multilínea no es una cosa.

En pocas palabras, no es pitónico. Del blog de Guido van Rossum:

Encuentro cualquier solución inaceptable que incorpora un bloque basado en sangría en el medio de una expresión. Como encuentro que la sintaxis alternativa para la agrupación de sentencias (por ejemplo, llaves o palabras clave de inicio / fin) es igualmente inaceptable, esto hace que una lambda de varias líneas sea un rompecabezas sin solución.

Samy Bencherif
fuente
10

Déjame presentarte un hack glorioso pero aterrador:

import types

def _obj():
  return lambda: None

def LET(bindings, body, env=None):
  '''Introduce local bindings.
  ex: LET(('a', 1,
           'b', 2),
          lambda o: [o.a, o.b])
  gives: [1, 2]

  Bindings down the chain can depend on
  the ones above them through a lambda.
  ex: LET(('a', 1,
           'b', lambda o: o.a + 1),
          lambda o: o.b)
  gives: 2
  '''
  if len(bindings) == 0:
    return body(env)

  env = env or _obj()
  k, v = bindings[:2]
  if isinstance(v, types.FunctionType):
    v = v(env)

  setattr(env, k, v)
  return LET(bindings[2:], body, env)

Ahora puede usar este LETformulario como tal:

map(lambda x: LET(('y', x + 1,
                   'z', x - 1),
                  lambda o: o.y * o.z),
    [1, 2, 3])

lo que da: [0, 3, 8]

divs1210
fuente
1
Publicado originalmente en gist.github.com/divs1210/d218d4b747b08751b2a232260321cdeb
divs1210
¡Esto es asombroso! Creo que usaré esto la próxima vez que escriba Python. Soy principalmente un programador de Lisp y JS, y la falta de lambada de varias líneas duele. Esta es una manera de conseguir eso.
Christopher Dumas
7

Soy culpable de practicar este truco sucio en algunos de mis proyectos, que es un poco más simple:

    lambda args...:( expr1, expr2, expr3, ...,
            exprN, returnExpr)[-1]

Espero que puedas encontrar una manera de mantenerte pitónico, pero si tienes que hacerlo es menos doloroso que usar un ejecutivo y manipular a los globales.

S.Rad
fuente
6

Permítanme tratar de abordar el problema de análisis de @balpha. Usaría paréntesis alrededor de lamda multilínea. Si no hay paréntesis, la definición lambda es codiciosa. Entonces la lambda en

map(lambda x:
      y = x+1
      z = x-1
      y*z,
    [1,2,3]))

devuelve una función que devuelve (y*z, [1,2,3])

Pero

map((lambda x:
      y = x+1
      z = x-1
      y*z)
    ,[1,2,3]))

medio

map(func, [1,2,3])

donde func es la lambda multilínea que devuelve y * z. ¿Eso funciona?

Wai Yip Tung
fuente
1
Estoy pensando que el superior debería regresar map(func, [1,2,3])y el inferior debería ser un error porque la función de mapa no tiene suficientes argumentos. También hay algunos paréntesis adicionales en el código.
Samy Bencherif
soltando eso en pycharm ejecutando python2.7.13 da un error de sintaxis
simbo1905
paréntesis extra
Samy Bencherif
4

(Para cualquiera que todavía esté interesado en el tema).

Considere esto (incluye incluso el uso de los valores de retorno de las declaraciones en otras declaraciones dentro de la lambda "multilínea", aunque es feo hasta el punto de vomitar ;-)

>>> def foo(arg):
...     result = arg * 2;
...     print "foo(" + str(arg) + ") called: " + str(result);
...     return result;
...
>>> f = lambda a, b, state=[]: [
...     state.append(foo(a)),
...     state.append(foo(b)),
...     state.append(foo(state[0] + state[1])),
...     state[-1]
... ][-1];
>>> f(1, 2);
foo(1) called: 2
foo(2) called: 4
foo(6) called: 12
12
vencik
fuente
Esto no funciona cuando se llama por segunda vez con diferentes parámetros y causa una pérdida de memoria a menos que la primera línea lo sea, state.clear()ya que los argumentos predeterminados solo se crean una vez cuando se crea la función.
Matthew D. Scholefield
1

Simplemente puede usar slash ( \) si tiene varias líneas para su función lambda

Ejemplo:

mx = lambda x, y: x if x > y \
     else y
print(mx(30, 20))

Output: 30
IRSHAD
fuente
La pregunta se refiere al uso de más de 1 expresión en lugar de más de 1 línea literal.
Tomás Zubiri
1

Estoy empezando con Python, pero viniendo de Javascript, la forma más obvia es extraer la expresión como una función ...

Ejemplo contribuido, la expresión de multiplicación (x*2)se extrae como función y, por lo tanto, puedo usar multilínea:

def multiply(x):
  print('I am other line')
  return x*2

r = map(lambda x : multiply(x), [1, 2, 3, 4])
print(list(r))

https://repl.it/@datracka/python-lambda-function

Tal vez no responda exactamente a la pregunta si eso fue cómo hacer multilínea en la expresión lambda en sí , pero en caso de que alguien obtenga este hilo buscando cómo depurar la expresión (como yo), creo que ayudará

Vicens Fayos
fuente
2
¿Por qué haría eso y no simplemente escribiría map(multiply, [1, 2, 3])?
Thothal
0

Sobre el tema de los hacks feos, siempre puedes usar una combinación de execy una función regular para definir una función multilínea como esta:

f = exec('''
def mlambda(x, y):
    d = y - x
    return d * d
''', globals()) or mlambda

Puede envolver esto en una función como:

def mlambda(signature, *lines):
    exec_vars = {}
    exec('def mlambda' + signature + ':\n' + '\n'.join('\t' + line for line in lines), exec_vars)
    return exec_vars['mlambda']

f = mlambda('(x, y)',
            'd = y - x',
            'return d * d')
Matthew D. Scholefield
fuente
0

Solo estaba jugando un poco para tratar de hacer una comprensión dict con reduce, y se me ocurrió este truco de línea:

In [1]: from functools import reduce
In [2]: reduce(lambda d, i: (i[0] < 7 and d.__setitem__(*i[::-1]), d)[-1], [{}, *{1:2, 3:4, 5:6, 7:8}.items()])                                                                                                                                                                 
Out[3]: {2: 1, 4: 3, 6: 5}

Solo estaba tratando de hacer lo mismo que se hizo en esta comprensión de dict Javascript: https://stackoverflow.com/a/11068265

rodfersou
fuente
0

Aquí hay una implementación más interesante de lambdas multilínea. No es posible lograrlo debido a cómo Python usa las sangrías como una forma de estructurar el código.

Pero afortunadamente para nosotros, el formato de sangría se puede deshabilitar usando matrices y paréntesis.

Como algunos ya señalaron, puede escribir su código como tal:

lambda args: (expr1, expr2,... exprN)

En teoría, si se garantiza que tendrá una evaluación de izquierda a derecha, funcionaría, pero aún así perderá los valores que se pasan de una expresión a otra.

Una forma de lograr lo que es un poco más detallado es tener

lambda args: [lambda1, lambda2, ..., lambdaN]

Donde cada lambda recibe argumentos del anterior.

def let(*funcs):
    def wrap(args):
        result = args                                                                                                                                                                                                                         
        for func in funcs:
            if not isinstance(result, tuple):
                result = (result,)
            result = func(*result)
        return result
    return wrap

Este método le permite escribir algo que es un poco lisp / esquema como.

Entonces puedes escribir cosas como esta:

let(lambda x, y: x+y)((1, 2))

Se podría usar un método más complejo para calcular la hipotenusa

lst = [(1,2), (2,3)]
result = map(let(
  lambda x, y: (x**2, y**2),
  lambda x, y: (x + y) ** (1/2)
), lst)

Esto devolverá una lista de números escalares para que pueda usarse para reducir múltiples valores a uno.

Tener tantos lambda ciertamente no va a ser muy eficiente, pero si estás limitado, puede ser una buena manera de hacer algo rápidamente y luego reescribirlo como una función real más adelante.

Loïc Faure-Lacroix
fuente
-3

porque se supone que una función lambda tiene una línea, ya que es la forma más simple de una función, an entrance, then return

Sphynx-HenryAY
fuente
1
No, cualquiera que escriba un programa basado en eventos le dirá que la lambda multilínea es importante.
Akangka