name - python pep 20
No Lambda multilínea en Python: ¿Por qué no? (9)
(Para cualquier persona aún interesada en el tema.)
Considere esto (incluye incluso el uso de los valores de retorno de las declaraciones en otras declaraciones dentro del 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
He oído decir que las lambdas multilínea no se pueden agregar en Python porque chocan sintácticamente con las otras construcciones de sintaxis en Python. Hoy pensé en esto en el autobús y me di cuenta de que no podía pensar en una sola construcción Python con la que las lambda 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 las lambdas multilínea en el lenguaje, pero por curiosidad: ¿cuál es una situación en la que incluir una lambda multilínea sería ambiguo? ¿Es verdad lo que he oído o hay alguna otra razón por la que Python no permite lambda multilínea?
Déjame tratar de abordar el problema de análisis de @balpha. Usaría paréntesis alrededor de la lamda multilínea. Si no hay paréntesis, la definición de lambda es codiciosa. Así que 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?
En el tema de hacks feos, siempre puedes usar una combinación de exec
y 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
Puedes 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'')
Esto generalmente es muy feo (pero a veces las alternativas son aún más feas), por lo que una solución es hacer una expresión de llaves:
lambda: (
doFoo(''abc''),
doBar(123),
doBaz())
Sin embargo, no aceptará ninguna asignación, por lo que tendrá que preparar los datos de antemano. El lugar en el que encontré esto útil es el contenedor PySide, donde a veces tiene devoluciones de llamadas 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())
Guido van Rossum (el inventor de Python) responde esta pregunta exacta en una antigua publicación del blog .
Básicamente, admite que es teóricamente posible, pero que cualquier solución propuesta sería no pitónica:
"Pero para mí, la complejidad de cualquier solución propuesta para este rompecabezas es inmensa: requiere que el analizador (o más precisamente, el lexer) sea capaz de alternar entre los modos sensible a la sangría e insensible a la sangría, manteniendo una pila de modos y nivel de sangría anteriores. Técnicamente eso se puede resolver (ya hay una pila de niveles de sangrado que pueden generalizarse). Pero nada de eso me quita la sensación de que todo es un artilugio elaborado por Rube Goldberg ".
Mira lo siguiente:
map(multilambda x:
y=x+1
return y
, [1,2,3])
¿Se trata de un retorno lambda (y, [1,2,3])
(por lo tanto, el mapa solo obtiene un parámetro, lo que genera un error)? ¿O regresa 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 importa en python, por lo que no puede trabajar sin ambigüedades con multilínea.
Esto es solo uno simple, probablemente hay más ejemplos.
Permítanme presentarles un truco glorioso pero aterrador:
lambda a, b: [(0, 9), (2, 3)][a<4][b>3]
Ahora puede utilizar este formulario LET
como tal:
lambda x: [n**2 for n in x] #Assuming x is a list or tuple in this case
lo que da: [0, 3, 8]
Un par de enlaces relevantes:
Por un tiempo, estuve siguiendo el desarrollo de Reia, que inicialmente iba a tener la sintaxis basada en la sangría de Python con bloques de Ruby también, todo sobre Erlang. Pero, el diseñador terminó renunciando a la sensibilidad de 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 + los bloques de líneas múltiples, y una mayor apreciación que obtuvo 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 los bloques de estilo Ruby en Python que encontré donde Guido publica una respuesta sin disparar (aunque no estoy seguro de si ha habido algún disparo posterior):
[Editar] Lee esta respuesta. Explica por qué la lambda multilínea no es una cosa.
En pocas palabras, es antipónico. De la publicación del blog de Guido van Rossum:
Me parece inaceptable cualquier solución que incruste un bloque basado en sangría en medio de una expresión. Como la sintaxis alternativa para la agrupación de sentencias (por ejemplo, llaves o palabras clave de inicio / finalización) es igualmente inaceptable, esto hace que un lambda multilínea sea un rompecabezas sin solución.
En cuanto al resto de esta respuesta. Utilice una lambda de una sola línea o una función con nombre. Por favor, no use exec
lamento haber sugerido eso.
1 Te sorprendería lo que puedes hacer con una línea de python.
Una solución para obtener funciones lambda multilínea (una extensión de la respuesta de skriticos):
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)
Que hace:
Python simplifica (ejecuta) todos los componentes de una tupla antes de leer los delimitadores.
por ejemplo,
lambda x: (functionA(), functionB(), functionC(), 0)[-1]
ejecutaría las tres funciones aunque la única información que se usa es el último elemento de la lista (0).Normalmente, no puede asignar o declarar variables dentro de listas o tuplas en python, sin embargo, con la función
exec
puede (tenga en cuenta que siempre devuelve:None
).Tenga en cuenta que, a menos que declare una variable como
global
, no existirá fuera de esa llamada a la funciónexec
(esto solo es cierto para las funcionesexec
dentro de las declaracioneslambda
).por ejemplo,
(lambda: exec(''x=5;print(x)''))()
funciona bien singlobal
declaraciónglobal
. Sin embargo,(lambda: (exec(''x=5''), exec(''print(x)'')))()
o(lambda: (exec(''x=5''), x)()
no.Tenga en cuenta que todasglobal
variablesglobal
se almacenan en el espacio de nombres global y seguirán existiendo después de que se complete la llamada a la función.Por esta razón, esta no es una buena solución y debe evitarse si es posible.global
variablesglobal
declaradas desde la funciónexec
dentro de una función lambda se mantienen separadas del espacio de nombresglobal
. (probado en Python 3.3.3)El
[-1]
al final de la tupla obtiene el último índice. Por ejemplo[1,2,3,4][-1]
es4
. Esto se hace para que solo se devuelva el (los) valor (es) de salida deseado (s) en lugar de una tupla completa que contengaNone
de las funcionesexec
y otros valores extraños.
Función multilínea equivalente:
map(lambda x: LET((''y'', x + 1,
''z'', x - 1),
lambda o: o.y * o.z),
[1, 2, 3])
Maneras de evitar la necesidad de un lambda multilínea:
Recursion
(lambda n: (exec(''global x; x=4; x=x+n''), x-2)[-1])(0)
Los booleanos son enteros:
def function(n):
x = 4
x = x+n
return x-2
function(0)
Iteradores
f = lambda i: 1 if i==0 or i==1 else f(i-1)+f(i-2)