¿Por qué Python no permite lambdas de varias líneas?

50

¿Alguien puede explicar las razones concretas por las que BDFL elige hacer una línea única de Python lambdas?

Esto es bueno:

lambda x: x**x

Esto da como resultado un error:

lambda x:
    x**x

Entiendo que hacer que lambda sea multilínea de alguna manera "perturbaría" las reglas de sangría normales y requeriría agregar más excepciones, pero ¿no vale la pena los beneficios?

Mire JavaScript, por ejemplo. ¿Cómo se puede vivir sin esas funciones anónimas? Son indispensables ¿No quieren los Pythonistas deshacerse de tener que nombrar cada función de varias líneas solo para pasarla como argumento?

codificador de árboles
fuente
3
Teniendo en cuenta que observa las razones concretas por las cuales Guido no permite lambdas de múltiples expresiones y luego las descarta, voy a suponer que está buscando validación en lugar de una respuesta real.
Jason Baker, el
3
Además de guardar siete caracteres, ¿cómo es esto mejor que un def? Ahora tiene exactamente la misma estructura visual.
desviarse el

Respuestas:

42

Guido van van Rossum lo respondió él mismo:

Pero tales soluciones a menudo carecen de "pitonicidad", ese rasgo esquivo de una buena característica de Python. Es imposible expresar Pythonicity como una restricción difícil. Incluso el Zen de Python no se traduce en una simple prueba de pitonicidad ...

En el ejemplo anterior, es fácil encontrar el talón de Aquiles de la solución propuesta: el doble colon, si bien es sintácticamente inequívoco (una de las "restricciones del rompecabezas"), es completamente arbitrario y no se parece a nada más en Python ...

Pero también lo estoy rechazando, porque al final (y aquí es donde admito haber engañado involuntariamente al remitente) encuentro inaceptable cualquier solución que incluya un bloque basado en la sangría en el medio de una expresión. Como encuentro que la sintaxis alternativa para la agrupación de instrucciones (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.

http://www.artima.com/weblogs/viewpost.jsp?thread=147358

Básicamente, dice que aunque una solución es posible, no es congruente con cómo es Python.

Veintiuna
fuente
2
+1 gracias por el enlace del hilo, pero aún agradecería las lambdas de varias líneas, son invaluables, mira JavaScript, PHP también las tiene incluidas.
treecoder
2
@greengit Puedes usar una definición anidada. No es lo mismo que las funciones anónimas, pero están lo suficientemente cerca.
jsternberg
2
defs anidados no ayudan al pasar funciones como argumentos - que es la principal razón por la que me gustaría lambds multilínea
treecoder
1
@greengit: creo que es mejor tomar esto con GvR que publicar sus comentarios aquí.
Jason Baker
99
@greengit: ¿Sabes que puedes pasar la función como argumento a otra función? No puede escribirlo en línea, pero no hay una técnica de programación que no esté disponible para usted.
btilly
24

está perfectamente bien hacer un lambda multilínea en python: ver

>>> f = lambda x: (
...   x**x)
>>> f
<function <lambda> at 0x7f95d8f85488>
>>> f(3)
27

La verdadera limitación lambda es el hecho de que lambda debe ser una sola expresión ; no puede contener palabras clave (como python2's printo return).

GvR elige hacerlo para limitar el tamaño de la lambda, ya que normalmente se usan como parámetros. Si quieres una función real, usadef

Vito De Tullio
fuente
1
la línea múltiple trata sobre la inserción del carácter '\ n': D python no tiene una declaración múltiple lambda. Realmente quieres usar def. Piénselo: ¿realmente necesita un invocable como parámetro de su función? ¿Y a los usuarios de esa función no se les permite pasar su llamada predeterminada? ¿Cómo pueden pasarlo si no se lo das?
Vito De Tullio
por cierto, ¿puedes dar un ejemplo de tu necesidad de una función anónima?
Vito De Tullio
1
Sí, encuentro la limitación de una sola expresión realmente frustrante. Es cierto que si permiten lambdas de múltiples expresiones, las personas ciertamente comenzarán a abusar de él, pero al revés es demasiado restrictivo omho.
rbaleksandar
10

Sé que esto es muy antiguo, pero lo pongo aquí como referencia.

Una alternativa al uso de lambda podría ser usar a defde una manera no convencional. El objetivo es pasar una deffunción a, lo que se puede hacer en una sola circunstancia: un decorador. Tenga en cuenta que con esta implementación def resultno crea una función, crea el resultado de reduce(), que termina siendo a dict.

Enchufe desvergonzado : hago mucho de esto aquí .

>>> xs = [('a', 1), ('b', 2), ('a', 3), ('b', 4)]
>>> foldl = lambda xs, initial: lambda f: reduce(f, xs, initial)
>>> @foldl(xs, {})
... def result(acc, (k, v)):
...     acc.setdefault(k, 0)
...     acc[k] += v
...     return acc
...
>>> result
{'a': 4, 'b': 6} 

Tenga en cuenta que se pueden hacer lambdas de varias declaraciones , pero solo con un código realmente muy feo. Sin embargo, lo interesante es cómo funciona el alcance con esta implementación (tenga en cuenta el uso múltiple de la namevariable y el sombreado de la messagevariable.

>>> from __future__ import print_function
>>> bind = lambda x, f=(lambda x: x): f(x)
>>> main = lambda: bind(
...     print('Enter your name.'), lambda _: bind(
...     raw_input('> '), lambda name: bind(
...     'Hello {}!'.format(name), lambda message: bind(
...     print(message), lambda _: bind(
...     'Bye {}!'.format(name), lambda message: bind(
...     print(message)
... ))))))
>>> main()
Enter your name.
> foo
Hello foo!
Bye foo!
pyrospade
fuente
+1 para un enfoque monádico
jozefg
Las mónadas también se llaman thenables o future / promises o incluso callbacks en JavaScript BTW.
aoeu256
3

Hackear juntos una lambda de múltiples declaraciones no es tan malo como lo hace Pyrospade: seguro que podríamos componer un montón de funciones monádicas usando bind, como en Haskell, pero como estamos en el mundo impuro de Python, también podríamos usa efectos secundarios para lograr lo mismo.

Cubro algunas maneras de hacer esto en mi blog .

Por ejemplo, Python garantiza evaluar los elementos de una tupla en orden, por lo que podemos usarlo ,como un imperativo ;. Podemos reemplazar muchas declaraciones, like print, con expresiones, like sys.stdout.write.

Por lo tanto, los siguientes son equivalentes:

def print_in_tag_def(tag, text):
    print "<" + tag + ">"
    print text
    print "</" + tag + ">"

import sys
print_ = sys.stdout.write
print_in_tag_lambda = lambda tag, text: (print_("<" + tag + ">"),
                                         print_(text),
                                         print_("</" + tag + ">"),
                                         None)[-1]

Tenga en cuenta que agregué un Noneal final y lo extraje usando [-1]; Esto establece el valor de retorno explícitamente. No tenemos que hacer esto, pero sin él obtendríamos un (None, None, None)valor de retorno funky , que nos puede interesar o no.

Entonces podemos secuenciar acciones IO. ¿Qué pasa con las variables locales?

Python =forma una declaración, por lo que necesitamos encontrar una expresión equivalente. Una forma es mutar el contenido de la estructura de datos, que se pasa como argumento. Por ejemplo:

def stateful_def():
    foo = 10
    bar = foo * foo
    foo = 2
    return foo + bar

stateful_lambda = (lambda state: lambda *_: (state.setdefault('foo', 10),
                                             state.setdefault('bar', state.get('foo') * state.get('foo')),
                                             state.pop('foo'),
                                             state.setdefault('foo', 2),
                                             state.get('foo') + state.get('bar'))[-1])({})

Hay algunos trucos que se utilizan en stateful_lambda:

  • El *_argumento permite que nuestra lambda tome cualquier número de argumentos. Como esto permite cero argumentos, recuperamos la convención de llamada de stateful_def.
    • Llamar un argumento _es solo una convención que dice "No voy a usar esta variable"
  • Tenemos una función ("envoltura") que devuelve otra función ("principal"): lambda state: lambda *_: ...
    • Gracias al alcance léxico , el argumento de la primera función estará dentro del alcance de la segunda función.
    • Aceptar algunos argumentos ahora y devolver otra función para aceptar el resto más tarde se conoce como curry
  • Inmediatamente llamamos a la función "wrapper", pasándole un diccionario vacío: (lambda state: ...)({})
    • Esto nos permite asignar una variable statea un valor {}sin usar una declaración de asignación (p. Ej. state = {})
  • Tratamos las claves y los valores statecomo nombres de variables y valores enlazados.
    • Esto es menos engorroso que usar lambdas llamadas inmediatamente
    • Esto nos permite mutar los valores de las variables.
    • Usamos en state.setdefault(a, b)lugar de a = by en state.get(a)lugar dea
  • Usamos una tupla para encadenar nuestros efectos secundarios, como antes
  • Usamos [-1]para extraer el último valor, que actúa como una returndeclaración

Por supuesto, esto es bastante engorroso, pero podemos hacer una API más agradable con funciones auxiliares:

# Keeps arguments and values close together for immediately-called functions
callWith = lambda x, f: f(x)

# Returns the `get` and `setdefault` methods of a new dictionary
mkEnv = lambda *_: callWith({},
                            lambda d: (d.get,
                                       lambda k, v: (d.pop(k), d.setdefault(k, v))))

# A helper for providing a function with a fresh `get` and `setdefault`
inEnv = lambda f: callWith(mkEnv(), f)

# Delays the execution of a function
delay = lambda f x: lambda *_: f(x)

# Uses `get` and `set`(default) to mutate values
stateful_lambda = delay(inEnv, lambda get, set: (set('foo', 10),
                                                 set('bar', get('foo') * get('foo')),
                                                 set('foo', 2),
                                                 get('foo') + get('bar'))[-1])
Warbo
fuente
estás bromeando, esto parece una pesadilla jajaja
Alexander Mills
1
@AlexanderMills Je, esto no fue pensado como un ejemplo del mundo real, más de una refutación de la lambda-en-lambda-en-lambda se aproximan, pyrospade para mostrar que las cosas no son que malo. De hecho, esto podría simplificarse mucho más ahora que tenemos python.org/dev/peps/pep-0572
Warbo
1

Pensé que podría contribuir, use un interruptor de línea:

x = lambda x,y: x-y if x<y \ 
                     else y-x if y<x \
                     else 0

No olvides lo muy bueno que Python puede escribir en líneas, como en el ejemplo:

a=b=0; c=b+a; d = a+b**2 #etc etc

Y el lambda es muy poderoso, pero no está destinado a la sustitución de 1 función completa, es decir, podría piratearlo como (ejemplo de préstamo del colega anterior):

makeTag = lambda tagName: "<{}>".format(tagName)
closeTag = lambda tagName: makeTag("/"+str(tagName))
openTag = lambda tagName: makeTag(tagName)
writeHMTLline = lambda tag,content: ""+opetTag(tag)+str(content)+closeTag(tag)

¿Pero realmente quieres hacerlo así? En su mayoría es ilegible después de algún tiempo, es como llegar al comienzo de la cuerda comenzando con el final desenredado. cuerda desenredada

Las lambdas se consideran funciones únicas, en el mapa, filtran y reducen funciones en la programación orientada funcional (entre otras cosas). Por ejemplo, obtener valores de caracteres de valores que son enteros y divisibles por 2

chrDev2 = lambda INT: chr(INT) if isinstance(INT,int) and INT%2==0 else INT
someStringList = map( chrDev2, range(30) )
>>> ['\x00', 1, '\x02', 3, '\x04', 5, '\x06', 7, '\x08', 9, '\n', 11, '\x0c', 13, '\x0e', 15, '\x10', 17, '\x12', 19, '\x14', 21, '\x16', 23, '\x18', 25, '\x1a', 27, '\x1c', 29]

Puede usarlo como función de expresión de funciones definiendo funciones complejas (o más y varias lambdas, y colocándolas dentro de otra lambda:

def someAnon(*args): return sum(list(args))
defAnon = lambda list: [ x*someAnon(*list) for x in list]

pero Python tiene soporte para expresiones de funciones de otra manera: -let dice que tiene alguna función llamada superAwesomeFunctiony esa función puede hacer cosas súper increíbles, puede asignarla a una variable al no llamarla, así:

SAF = superAwesomeFunction # there is no () at the end, 

Entonces, cuando llame a SAF, llamará a superAwesomeFunction o método. Si busca a través de su carpeta Lib, puede encontrar que la mayoría de los __builtin__módulos de Python están escritos de esa manera. Esto se hace porque a veces necesitará algunas funciones que realicen tareas específicas que no son lo suficientemente necesarias para que el usuario pueda usarlas, pero es necesario para varias funciones. Entonces, tiene la opción de no tener 2 funciones con el nombre "superAwesomeFunction", puede tener "superAwesomeFunctionDoingBasicStuf" y "realSuperAwesomeFunction" y simplemente poner la variable "realSuperAwesomeFunction" en "superAwesomeFunction" y listo.

Puede encontrar la ubicación de los módulos importados ingresando en la consola importedModule.__file__(ejemplo real import os;os.__file__), y simplemente siga ese directorio al archivo llamado importModule.py y ábralo en el editor y descubra cómo puede maximizar su propio "conocimiento".

Espero que esto te ayude a ti y quizás a otros colegas en problemas.

Danilo
fuente