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


50

Alguém pode explicar as razões concretas pelas quais a BDFL escolhe criar lambdas em Python em uma única linha?

Isso é bom:

lambda x: x**x

Isso resulta em um erro:

lambda x:
    x**x

Entendo que a criação de várias linhas lambda de alguma forma "perturbaria" as regras normais de indentação e exigiria a adição de mais exceções, mas isso não vale os benefícios?

Veja o JavaScript, por exemplo. Como alguém pode viver sem essas funções anônimas? Eles são indispensáveis. Os Pythonistas não querem se livrar de ter que nomear todas as funções de várias linhas apenas para passar isso como argumento?


3
Considerando que você observa as razões concretas pelas quais o Guido não permite lambdas de múltiplas expressões e as dispensa, vou assumir que você está buscando validação em vez de uma resposta real.
Jason Baker

3
Além de salvar sete caracteres, como isso é melhor do que um def? Agora ele tem exatamente a mesma estrutura visual.
detly 8/08/08

Respostas:


42

Guido van van Rossum respondeu:

Mas essas soluções geralmente carecem de "Pitonicidade" - essa característica ilusória de um bom recurso do Python. É impossível expressar a pitonicidade como uma restrição difícil. Mesmo o Zen do Python não se traduz em um simples teste de Pitonicidade ...

No exemplo acima, é fácil encontrar o calcanhar de Aquiles da solução proposta: o cólon duplo, embora seja sintaticamente inequívoco (uma das "restrições de quebra-cabeças"), é completamente arbitrário e não se assemelha a nada em Python ...

Mas estou rejeitando isso também, porque no final (e é aqui que admito enganar involuntariamente o remetente), acho qualquer solução inaceitável que incorpore um bloco baseado em indentação no meio de uma expressão. Como acho a sintaxe alternativa para o agrupamento de instruções (por exemplo, chaves ou palavras-chave de início / fim) igualmente inaceitável, isso praticamente torna um lambda de várias linhas um quebra-cabeça insolúvel.

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

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


2
+1 obrigado pelo link do thread - mas eu ainda apreciaria lambdas de várias linhas - elas são inestimáveis ​​- veja JavaScript, o PHP as inclui também.
treecoder

2
@greengit Você pode usar apenas uma definição aninhada. Não é o mesmo que funções anônimas, mas elas estão próximas o suficiente.
jsternberg

2
defs aninhados não ajudam ao passar funções como argumentos - que é a principal razão por que eu gostaria lambds multi-linha
treecoder

11
@greengit - Acho melhor você aceitar isso com o GvR do que postar seus comentários aqui.
Jason Baker

9
@ greengit: Você sabe que pode passar a função como argumento para outra função? Você não pode escrevê-lo em linha, mas não há técnica de programação que não esteja disponível para você.
btilly

24

é perfeitamente bom fazer um lambda de várias linhas em python: veja

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

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

O GvR decide fazer isso para limitar o tamanho do lambda, pois eles normalmente são usados ​​como parâmetros. Se você deseja uma função real, usedef


11
multi- line é sobre a inserção do caractere '\ n': D python não possui lambda de multi- instrução . Você realmente quer usar def. Pense bem: você realmente precisa de um código de chamada como parâmetro de sua função? E os usuários dessa função não têm permissão para transmitir seu valor padrão que pode ser chamado? Como eles podem passar se você não dá a eles?
Vito De Tullio

btw, você pode fornecer um exemplo de sua necessidade de uma função anônima?
Vito De Tullio

11
Sim, acho a limitação de uma única expressão realmente frustrante. É verdade que, se eles permitirem lambdas de múltiplas expressões, as pessoas certamente começarão a abusar, mas o contrário será muito restritivo.
Rbaleksandar

10

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

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

Plugue sem vergonha : 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} 

Observe que lambdas com várias instruções podem ser feitas, mas apenas com código muito, muito feio. No entanto, o interessante é como o escopo funciona com essa implementação (observe o uso múltiplo da namevariável e o sombreamento da messagevariável.

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

+1 para uma abordagem monádico
jozefg

Mônadas também são chamadas de tabelas ou futuras / promessas ou até retornos de chamada em JavaScript BTW.
aoeu256 11/09

3

Hackear um lambda com várias instruções não é tão ruim quanto a pyrospade: certamente poderíamos compor um monte de funções monádicas usando o bind, como em Haskell, mas como estamos no mundo impuro do Python, podemos também use efeitos colaterais para conseguir a mesma coisa.

Cubro algumas maneiras de fazer isso no meu blog .

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

Portanto, o seguinte é equivalente:

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 eu adicionei um Noneno final e o extraí usando [-1]; isso define o valor de retorno explicitamente. Não precisamos fazer isso, mas sem ele obteríamos um (None, None, None)valor de retorno desagradável , com o qual podemos ou não nos importar.

Para que possamos sequenciar as ações de IO. E as variáveis ​​locais?

Como o Python =forma uma declaração, precisamos encontrar uma expressão equivalente. Uma maneira é alterar o conteúdo da estrutura de dados, transmitida como 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 aceite qualquer número de argumentos. Como isso permite zero argumentos, 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") retornando outra função ("main"): lambda state: lambda *_: ...
    • Graças ao escopo lexical , o argumento da primeira função estará dentro do escopo para a segunda função
    • Aceitar alguns argumentos agora e retornar outra função para aceitar o restante mais tarde é conhecido como currying
  • Chamamos imediatamente a função "wrapper", passando para ela um dicionário vazio: (lambda state: ...)({})
    • Isso nos permite atribuir uma variável statea um valor {}sem usar uma declaração de atribuição (por exemplo, state = {})
  • Tratamos chaves e valores statecomo nomes de variáveis ​​e valores vinculados
    • Isso é menos complicado do que usar lambdas chamadas imediatamente
    • Isso nos permite alterar os valores das variáveis
    • Nós usamos em state.setdefault(a, b)vez de a = be em state.get(a)vez dea
  • Usamos uma tupla para encadear nossos efeitos colaterais, como antes
  • Usamos [-1]para extrair o último valor, que age como uma returndeclaração

Claro que isso é bastante complicado, mas podemos criar uma API melhor 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])

você está brincando, isso parece um pesadelo lol
Alexander Mills

11
@AlexanderMills Heh, isso não foi concebido como um exemplo do mundo real, mais de uma refutação da-lambdas-in lambdas-in-lambdas aproximar, pyrospade para mostrar que as coisas não são que ruim. De fato, isso poderia ser simplificado muito mais agora que temos python.org/dev/peps/pep-0572
Warbo 7/18

1

Embora eu possa contribuir, use um disjuntor de linha:

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

Não esqueça a coisa muito agradável que 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 à substituição de uma função inteira, quero dizer que você pode hacká-lo como (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 fim desvendado. corda desembaraçada

As lambdas são usadas como funções únicas, no mapa, filtram e reduzem funções na Programação Orientada a Funcionalidade (entre outras coisas). Por exemplo, obter valores de caracteres de números 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ê pode usá-lo como função de expressão de função, alterando a função complexa (ou mais e várias lambdas e colocando-a dentro de outra lambda:

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

mas o Python tem suporte a expressões de função de outra maneira: -vemos que você tem alguma função chamada superAwesomeFunctione essa função pode fazer coisas super impressionantes, você pode atribuí-lo a uma variável sem chamá-lo, assim:

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

Então agora, quando você chama SAF, chama superAwesomeFunction ou método. Se você pesquisar na sua pasta Lib, poderá descobrir que a maioria dos __builtin__módulos python é escrita 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 utilizadas pelo usuário, mas que são necessárias para várias funções. Então, você pode optar por não ter 2 funções com o nome "superAwesomeFunction", pode ter "superAwesomeFunctionDoingBasicStuf" e "realSuperAwesomeFunction" e, em seguida, basta colocar a variável "realSuperAwesomeFunction" na variável "superAwesomeFunction" e pronto.

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 ImportModule.py e abri-lo no editor e descobrir como você pode maximizar seu próprio "conhecimento".

Espero que isso ajude você e talvez outros colegas com problemas.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.