¿Por qué Python no permite lambdas multilínea?

43

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

Esto es bueno:

lambda x: x**x

Esto produce un error:

lambda x:
    x**x

Entiendo que hacer lambda 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?

Mira a JavaScript, por ejemplo. ¿Cómo se puede vivir sin esas funciones anónimas? Son indispensables. ¿Los Pythonistas no quieren deshacerse de tener que nombrar cada función multilínea solo para pasarla como un argumento?

    
pregunta treecoder 07.08.2011 - 19:30

5 respuestas

42

Guido van van Rossum lo respondió él mismo:

  

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

     

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 en nada a Python ...

     

Pero también estoy rechazando eso, porque al final (y aquí es donde admito que engañé involuntariamente al remitente), encuentro cualquier solución inaceptable que incrusta un bloque basado en sangría en la mitad de una expresión. Como la sintaxis alternativa para la agrupación de sentencias (por ejemplo, llaves o palabras clave de inicio / final) es igualmente inaceptable, esto hace que un lambda multilínea sea un rompecabezas sin solución.

enlace

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

    
respondido por el BlackJack 07.08.2011 - 19:39
22

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 limitación lambda real es el hecho de que lambda debe ser una única expresión ; no puede contener palabras clave (como print o return de python2).

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

    
respondido por el Vito De Tullio 07.08.2011 - 20:03
8

Sé que esto es super viejo, pero lo pongo aquí como referencia.

Una alternativa al uso de lambda podría ser usar un def de una manera no convencional. El objetivo es pasar un def a una función, que se puede hacer en una sola circunstancia: un decorador. Tenga en cuenta que con esta implementación def result no crea una función, crea el resultado de reduce() , que termina siendo un 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 las lambdas de varias declaraciones se pueden hacer, pero solo con un código realmente feo. Sin embargo, lo interesante es cómo funciona el alcance con esta implementación (tenga en cuenta el uso múltiple de la variable name y el sombreado de la variable message .

>>> 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!
    
respondido por el pyrospade 16.11.2013 - 02:57
3

La piratería conjunta de un lambda de varias declaraciones no es tan mala como Pyrospade: estamos seguros de que podríamos componer un montón de funciones monádicas usando bind, como en Haskell, pero ya que estamos en En el mundo impuro de Python, podríamos usar efectos secundarios para lograr lo mismo.

Cubro algunas formas de hacer esto en mi blog .

Por ejemplo, Python garantiza evaluar los elementos de una tupla en orden, por lo que podemos usar , como un imperativo ; . Podemos reemplazar muchas declaraciones, como print , con expresiones, como 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é None al final y lo extraje utilizando [-1] ; esto establece el valor de retorno explícitamente. No tenemos que hacer esto, pero sin ello obtendríamos un valor de retorno funky (None, None, None) , que puede que nos importe o no.

Así podemos secuenciar acciones de IO. ¿Qué pasa con las variables locales?

El = de 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 un 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])({})

Se están utilizando algunos trucos en stateful_lambda :

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

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

# 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])
    
respondido por el Warbo 06.08.2014 - 22:38
1

Aunque pensé que podría contribuir, use un separador de línea:

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

No olvides la cosa muy bonita que Python puede escribir en los lineares, 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, quiero decir que podrías piratearlo (ejemplo de préstamo de un colega de arriba):

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 parte, es ilegible después de algún tiempo, es como llegar al principio de la cuerda comenzando con el final desenredado.

LasLambdassonfuncionesúnicas,enelmapa,filtranyreducenfuncionesenlaProgramaciónOrientadaFuncional(entreotrascosas).Porejemplo,obtenervaloresdecaracteresdevaloresquesonenterosydivisiblespor2

chrDev2=lambdaINT:chr(INT)ifisinstance(INT,int)andINT%2==0elseINTsomeStringList=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]

Sepodríausarcomofuncióndeexpresióndefuncionesdefiniendolafuncióncompleja(omásyvariaslambdas,ycolocándoladentrodeotralambda:

defsomeAnon(*args):returnsum(list(args))defAnon=lambdalist:[x*someAnon(*list)forxinlist]

peroPythontienesoportedeexpresionesdefuncionesdeotramanera:-LoscomentariosdicenquetienesunafunciónllamadasuperAwesomeFunctionyesafunciónpuedehacercosasincreíbles,puedesasignarlaaunavariablealnollamarla,deestaforma:

SAF=superAwesomeFunction#thereisno()attheend,

Entonces,cuandollameaSAF,llamaráasuperAwesomeFunctionomethod.SibuscaensucarpetaLib,puedeencontrarquelamayoríadelosmódulosdepython__builtin__estánescritosdeesamanera.Estosehaceporqueavecesnecesitaráalgunasfuncionesquerealizantareasespecíficasquenosonlosuficientementenecesariasparaquepuedanserutilizadasporelusuario,perosonnecesariasparavariasfunciones.Entonces,tieneunaopciónquenopuedetener2funcionesconelnombre"superAwesomeFunction", puede tener "superAwesomeFunctionDoingBasicStuf" y "realSuperAwesomeFunction" y que simplemente coloque la "realSuperAwesomeFunction" en la variable "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 importsModule.py y ábralo en el editor y Encuentra cómo puedes maximizar tu propio "conocimiento".

Espero que esto te ayude a ti y tal vez a otros colegas en problemas.

    
respondido por el Danilo 23.12.2016 - 20:52

Lea otras preguntas en las etiquetas