Por que o Python não permite lambdas de várias linhas?

44

Alguém pode explicar as razões concretas pelas quais o BDFL escolheu fazer uma única linha de lambdas em Python?

Isso é bom:

lambda x: x**x

Isso resulta em um erro:

lambda x:
    x**x

Eu entendo que fazer lambda multilinha iria de alguma forma "atrapalhar" as regras normais de indentação e exigiria mais exceções, mas isso não vale os benefícios?

Veja o JavaScript, por exemplo. Como se pode viver sem essas funções anônimas? Eles são indispensáveis. Os Pythonistas não querem se livrar de ter que nomear cada função multi-line apenas para passá-la como argumento?

    
por treecoder 07.08.2011 / 19:30
fonte

5 respostas

42

Guido van van Rossum respondeu a si mesmo:

But such solutions often lack "Pythonicity" -- that elusive trait of a good Python feature. It's impossible to express Pythonicity as a hard constraint. Even the Zen of Python doesn't translate into a simple test of Pythonicity...

In the example above, it's easy to find the Achilles heel of the proposed solution: the double colon, while indeed syntactically unambiguous (one of the "puzzle constraints"), is completely arbitrary and doesn't resemble anything else in Python...

But I'm rejecting that too, because in the end (and this is where I admit to unintentionally misleading the submitter) I find any solution unacceptable that embeds an indentation-based block in the middle of an expression. Since I find alternative syntax for statement grouping (e.g. braces or begin/end keywords) equally unacceptable, this pretty much makes a multi-line lambda an unsolvable puzzle.

link

Basicamente, ele diz que, embora uma solução seja possível, ela não é congruente com a forma como o Python é.

    
por 07.08.2011 / 19:39
fonte
22

é perfeitamente correto fazer um lambda multi-line em python: veja

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

a verdadeira limitação lambda é o fato de que lambda deve ser uma única expressão ; não pode conter palavra-chave (como print ou return do python2).

GvR opta por fazer isso para limitar o tamanho do lambda, já que eles normalmente são usados como parâmetros. Se você quiser uma função real, use def

    
por 07.08.2011 / 20:03
fonte
8

Eu sei que isso é super antigo, mas colocar aqui como referência.

Uma alternativa ao uso de lambda poderia ser usar um def de maneira não convencional. O objetivo é passar um def para uma função, o que pode ser feito em apenas uma circunstância - um decorador. Observe que com essa implementação def result não cria uma função, ela cria o resultado de reduce() , que acaba sendo dict .

Plug sem graça : eu faço muito isso aqui .

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

Note que lambdas multi-declarações podem ser feitas, mas apenas com código realmente muito feio. No entanto, o que é interessante é como o escopo funciona com essa implementação (observe o uso múltiplo da variável name e o sombreamento da variável 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!
    
por 16.11.2013 / 02:57
fonte
3

Hackear um lambda multi-statement não é tão ruim quanto o Pyrospade faz: com certeza nós poderíamos compor um monte de funções monádicas usando bind, como em Haskell, mas já que estamos o mundo impuro do Python, podemos também usar efeitos colaterais para alcançar a mesma coisa.

Eu cubro algumas maneiras de fazer isso no meu blog .

Por exemplo, o Python garante avaliar os elementos de uma tupla em ordem, então podemos usar , como um imperativo ; . Podemos substituir muitas instruções, como print , por expressões, como sys.stdout.write .

Por isso, os seguintes são 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]

Observe que adicionei um None no final e extraí-lo usando [-1] ; isso define o valor de retorno explicitamente. Não precisamos fazer isso, mas sem isso teríamos um valor de retorno (None, None, None) , que podemos ou não nos importar.

Assim, podemos sequenciar ações IO. E quanto às variáveis locais?

O = do Python forma uma declaração, por isso precisamos encontrar uma expressão equivalente. Uma maneira é alterar o conteúdo da estrutura de dados, passado como um argumento. Por exemplo:

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])({})

Existem alguns truques sendo usados em stateful_lambda :

  • O argumento *_ permite que o nosso lambda receba qualquer número de argumentos. Como isso permite os argumentos zero , recuperamos a convenção de chamada de stateful_def .
    • Chamar um argumento _ é apenas uma convenção que diz "não vou usar essa variável"
  • Temos uma função ("wrapper") que retorna outra função ("main"): lambda state: lambda *_: ...
    • Graças ao escopo léxico , o argumento da primeira função estará dentro do escopo do segunda função
    • Aceitar alguns argumentos agora e retornar outra função para aceitar o restante depois é conhecido como curry
  • Nós imediatamente chamamos a função "wrapper", passando um dicionário vazio: (lambda state: ...)({})
    • Isso nos permite atribuir uma variável state a um valor {} sem usar uma instrução de atribuição (por exemplo, state = {} )
  • Tratamos chaves e valores em state como nomes de variáveis e valores vinculados
    • Isso é menos complicado do que usar imediatamente chamado lambdas
    • Isso nos permite alterar os valores das variáveis
    • Usamos state.setdefault(a, b) em vez de a = b e state.get(a) em vez de a
  • Usamos uma tupla para encadear nossos efeitos colaterais, como antes
  • Usamos [-1] para extrair o último valor, que age como uma declaração return

É claro que isso é bastante complicado, mas podemos criar uma API mais agradável com funções 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])
    
por 06.08.2014 / 22:38
fonte
1

Embora eu possa contribuir, use um separador de linha:

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

Não se esqueça da coisa muito boa que o python é capaz de escrever oneliners, como no exemplo:

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

E o lambda é muito poderoso, mas não se destina a substituir 1 função inteira, ou seja, você pode usar o exemplo acima (exemplo de empréstimo do colega acima):

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)

Mas você realmente quer fazer assim? É quase ilegível depois de algum tempo, é como chegar ao início da corda, começando com o final desvendado.

As Lambdas são usadas como funções únicas, em mapear, filtrar e reduzir funções em Programação Orientada Funcional (entre outras coisas). Por exemplo, obtendo valores de caractere de valores que são inteiros e divisíveis 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]

Você poderia usá-lo como função exprime a função, definindo a função complexa (ou mais e vários lambdas, e colocando-a dentro de outro lambda:

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

mas o Python tem função expressa o suporte de outra maneira: -letes dizem que você tem alguma função chamada superAwesomeFunction e que a função pode fazer algumas coisas super incríveis, você pode atribuí-la a uma variável não chamando, assim:

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

Agora, quando você chamar SAF, chamará superAwesomeFunction ou method. Se você pesquisar através da sua pasta Lib você pode descobrir que a maioria dos módulos python __builtin__ são escritos dessa maneira. Isso é feito porque às vezes você precisará de algumas funções que executam tarefas específicas que não são necessárias o suficiente para serem usadas pelo usuário, mas que são necessárias para várias funções. Então você tem uma escolha que você não pode ter 2 funções com o nome "superAwesomeFunction", você pode ter "superAwesomeFunctionDoingBasicStuf" e "realSuperAwesomeFunction" e que basta colocar o "realSuperAwesomeFunction" na variável "superAwesomeFunction" e está feito.

Você pode encontrar a localização dos módulos importados digitando no console importedModule.__file__ (exemplo real import os;os.__file__ ), e basta seguir esse diretório para o arquivo chamado importedModule.py e abri-lo no editor e Descubra como você pode maximizar seu próprio "conhecimento".

Espero que isso ajude você e talvez outros colegas em apuros.

    
por 23.12.2016 / 20:52
fonte

Tags