Não há Lambda multilinha no Python: por que não?


335

Ouvi dizer que lambdas multilinhas não podem ser adicionadas no Python porque elas colidiriam sintaticamente com outras construções de sintaxe no Python. Eu estava pensando sobre isso no ônibus hoje e percebi que não conseguia pensar em uma única construção Python com a qual as lambdas multilinhas se chocassem. Dado que conheço bem o idioma, isso me surpreendeu.

Agora, tenho certeza de que Guido tinha um motivo para não incluir lambdas multilinhas no idioma, mas por curiosidade: qual é a situação em que incluir uma lambda multilinha seria ambíguo? É o que eu ouvi falar verdade, ou há algum outro motivo pelo qual o Python não permite lambdas multilinhas?


12
tl; dr version: porque o Python é uma linguagem lenta sem {} blocos e, portanto, isso não era permitido para manter um design sintático consistente.
Andrew

11
Também: Estou completamente surpreso que ninguém tenha mencionado isso nas respostas ... Você pode terminar as linhas com o caractere \ no Python e continuar na próxima linha ... Essas informações substituem toda essa pergunta, então ...
Andrew


"design sintático"
nicolas

Isso exigiria permitir declarações dentro de expressões. Se você vai fazer isso, não precisa de lambdaexpressões em primeiro lugar; você pode simplesmente usar definstruções em expressões.
Chepner

Respostas:


153

Veja o seguinte:

map(multilambda x:
      y=x+1
      return y
   , [1,2,3])

É um retorno lambda (y, [1,2,3])(assim, o mapa obtém apenas um parâmetro, resultando em um erro)? Ou volta y? Ou é um erro de sintaxe, porque a vírgula na nova linha está incorreta? Como Python saberia o que você quer?

Dentro da parênteses, a indentação não importa para python, então você não pode trabalhar sem ambiguidade com multilinhas.

Este é apenas um exemplo simples, provavelmente há mais exemplos.


107
eles podem forçar o uso de parênteses se você desejar retornar uma tupla de uma lambda. Na IMO, isso sempre deveria ser aplicado para evitar essas ambiguidades, mas tudo bem.
MJ

26
Essa é uma ambiguidade simples que deve ser resolvida adicionando um conjunto extra de parênteses, algo que já existe em muitos lugares, por exemplo, expressões geradoras cercadas por outros argumentos, chamando um método em um literal inteiro (embora isso não precise ser o caso, pois o nome da função não pode começar com um dígito) e, é claro, também lambdas de linha única (que podem ser expressões longas escritas em várias linhas). As lambdas com várias linhas não seriam especialmente diferentes desses casos que justificam excluí-las nessa base. Esta é a verdadeira resposta.
nmclean

3
Eu gosto de como há línguas zilhão que lidar com isso sem traste, mas de alguma forma há algumas razões profundas por que se não é supostamente muito difícil impossível
nicolas

1
@nicolas que é python em poucas palavras
javadba

Razão pela qual eu não uso lambda, tão sub-desenvolvido em Python.
NoName 13/01

635

Guido van Rossum (o inventor do Python) responde a essa pergunta exata em um antigo post de blog .
Basicamente, ele admite que é teoricamente possível, mas que qualquer solução proposta seria não-pitonica:

"Mas a complexidade de qualquer solução proposta para esse quebra-cabeça é imensa para mim: exige que o analisador (ou mais precisamente, o lexer) seja capaz de alternar entre os modos sensível ao recuo e insensível ao recuo, mantendo uma pilha dos modos anteriores e do nível de indentação. Tecnicamente, tudo isso pode ser resolvido (já existe uma pilha de níveis de indentação que pode ser generalizada). Mas nada disso tira o meu pressentimento de que tudo é uma engenhoca elaborada de Rube Goldberg ".


108
Por que essa não é a resposta principal? Não se trata de razões técnicas, é uma escolha de design, conforme claramente declarado pelo inventor.
precisa

13
@ DanAbramov porque o OP não fez login por anos provavelmente.
O contrato do Prof. Falken violou

7
Para aqueles que não entenderam a referência de Rube Goldberg, consulte: en.wikipedia.org/wiki/Rube_Goldberg_Machine
fjsj

56
A resposta de Guido é apenas outra razão pela qual desejo que o Python não dependesse do recuo para definir blocos.
LS

25
Não tenho certeza se chamaria "pressentimento" uma opção de design. ;)
Elliot Cameron

54

Isso geralmente é muito feio (mas às vezes as alternativas são ainda mais feias), portanto, uma solução alternativa é fazer uma expressão entre chaves:

lambda: (
    doFoo('abc'),
    doBar(123),
    doBaz())

Porém, ele não aceita nenhuma atribuição, portanto, você deverá preparar os dados com antecedência. O local que achei útil é o wrapper PySide, onde às vezes você tem retornos curtos. Escrever funções-membro adicionais seria ainda mais feio. Normalmente você não precisará disso.

Exemplo:

pushButtonShowDialog.clicked.connect(
    lambda: (
    field1.clear(),
    spinBox1.setValue(0),
    diag.show())

2
Meu chefe estava apenas pedindo algo assim em nosso aplicativo PyQt. Impressionante!
TheGerm 26/01

1
Obrigado por isso, eu também estava procurando uma boa maneira de usar lambdas curtas (mas ainda com várias linhas) como retornos de chamada para a interface do usuário do PySide.
Michael Leonard

E agora que eu vi isso, sugeriu instantaneamente usar lambda arge setattr(arg, 'attr','value')subverter "sem atribuições ...". E depois há avaliação de curto-circuito ande or... é o Javascript que faz isso. Afunda raízes em você, como hera em uma parede. Eu quase espero esquecer isso no Natal.
nigel222

bastante inteligente - e bastante legível. Agora - sobre essas tarefas (ausentes ..) ..
javadba 9/03

@ nigel222 por que sentir vergonha? A linguagem python é fundamentalmente prejudicada - mas é usada de qualquer maneira para grande parte da ciência de dados . Então, fazemos ajustes. Encontrar maneiras de causar efeitos colaterais (geralmente basta imprimir / registrar!) E atribuições (geralmente o suficiente para apenas intermediários!) Devem ser bem tratados pelo idioma. Mas eles nem são suportados (pelo menos se você seguir as PEPdiretrizes)
javadba

17

Alguns links relevantes:

Por um tempo, eu estava acompanhando o desenvolvimento do Reia, que inicialmente teria a sintaxe baseada em indentação do Python com blocos Ruby também, tudo sobre Erlang. Mas, o designer acabou desistindo da sensibilidade do recuo, e este post que ele escreveu sobre essa decisão inclui uma discussão sobre problemas com os quais ele se deparou com recuo + blocos de várias linhas e uma apreciação maior que ele ganhou pelas questões / decisões de design de Guido:

http://www.unlimitednovelty.com/2009/03/indentation-sensitivity-post-mortem.html

Além disso, aqui está uma proposta interessante para os blocos de estilo Ruby no Python que encontrei em Guido, onde uma resposta foi lançada sem realmente derrubá-la (não tenho certeza se houve alguma derrubada subsequente):

http://tav.espians.com/ruby-style-blocks-in-python.html


12

[Editar] Leia esta resposta. Explica por que lambda multilinha não é uma coisa.

Simplificando, é atípico. Da publicação de blog de Guido van Rossum:

Acho inaceitável qualquer solução que incorpore um bloco baseado em recuo 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.


10

Deixe-me apresentar um corte glorioso, mas aterrador:

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)

Agora você pode usar este LETformulário da seguinte forma:

map(lambda x: LET(('y', x + 1,
                   'z', x - 1),
                  lambda o: o.y * o.z),
    [1, 2, 3])

que dá: [0, 3, 8]



Isso é incrível! Acho que vou usar isso na próxima vez que escrever Python. Sou principalmente um programador Lisp e JS, e a falta de lambada de várias linhas dói. Esta é uma maneira de conseguir isso.
Christopher Dumas

7

Sou culpado de praticar esse truque sujo em alguns dos meus projetos, que é um pouco mais simples:

    lambda args...:( expr1, expr2, expr3, ...,
            exprN, returnExpr)[-1]

Espero que você consiga encontrar uma maneira de se manter pythonic, mas se você tiver que fazer isso menos doloroso do que usar exec e manipular globals.


6

Deixe-me tentar resolver o problema de análise do @balpha. Eu usaria parênteses ao redor da lamda multilinha. Se não houver parênteses, a definição lambda é gananciosa. Então a lambda em

map(lambda x:
      y = x+1
      z = x-1
      y*z,
    [1,2,3]))

retorna uma função que retorna (y*z, [1,2,3])

Mas

map((lambda x:
      y = x+1
      z = x-1
      y*z)
    ,[1,2,3]))

significa

map(func, [1,2,3])

onde func é a lambda multilinha que retorna y * z. Isso funciona?


1
Eu estou pensando que o superior deve retornar map(func, [1,2,3])e o inferior deve ser um erro porque a função de mapa não possui argumentos suficientes. Também há alguns parênteses extras no código.
Samy Bencherif

soltá-lo no pycharm executando python2.7.13, gera um erro de sintaxe.
simbo1905

parêntese extra
Samy Bencherif

4

(Para quem ainda estiver interessado no tópico.)

Considere isso (inclui o uso uniforme dos valores de retorno das declarações em outras declarações dentro da lambda "multilinha", embora seja feio a ponto 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

Isso não funciona quando chamado pela segunda vez com parâmetros diferentes e causa um vazamento de memória, a menos que a primeira linha seja state.clear()porque os argumentos padrão são criados apenas uma vez quando a função é criada.
Matthew D. Scholefield

1

Você pode simplesmente usar slash ( \) se tiver várias linhas para sua função lambda

Exemplo:

mx = lambda x, y: x if x > y \
     else y
print(mx(30, 20))

Output: 30

A questão se preocupa em usar mais de 1 expressão em vez de mais de 1 linha literal.
Tomas Zubiri

1

Estou começando com python, mas vindo do Javascript, a maneira mais óbvia é extrair a expressão como uma função ....

Exemplo artificial, a expressão multiplicar (x*2)é extraída como função e, portanto, eu posso usar várias linhas:

def multiply(x):
  print('I am other line')
  return x*2

r = map(lambda x : multiply(x), [1, 2, 3, 4])
print(list(r))

https://repl.it/@datracka/python-lambda-function

Talvez não responda exatamente à pergunta se foi assim que fazer multilinha na própria expressão lambda , mas caso alguém obtenha esse encadeamento procurando depurar a expressão (como eu), acho que ajudará


2
Por que eu faria isso e não simplesmente escreveria map(multiply, [1, 2, 3])?
thothal 19/03

0

No assunto de hacks feios, você sempre pode usar uma combinação de exece uma função regular para definir uma função de múltiplas linhas como esta:

f = exec('''
def mlambda(x, y):
    d = y - x
    return d * d
''', globals()) or mlambda

Você pode agrupar isso em uma função 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')

0

Eu estava apenas brincando um pouco para tentar entender o ditado com o reduzir e criar esse truque de liner:

In [1]: from functools import reduce
In [2]: reduce(lambda d, i: (i[0] < 7 and d.__setitem__(*i[::-1]), d)[-1], [{}, *{1:2, 3:4, 5:6, 7:8}.items()])                                                                                                                                                                 
Out[3]: {2: 1, 4: 3, 6: 5}

Eu estava apenas tentando fazer o mesmo que foi feito nesta compreensão de dict Javascript: https://stackoverflow.com/a/11068265


0

Aqui está uma implementação mais interessante de lambdas de várias linhas. Não é possível obter isso devido ao modo como o python usa recuos como uma maneira de estruturar o código.

Felizmente, para nós, a formatação de recuo pode ser desativada usando matrizes e parênteses.

Como alguns já apontaram, você pode escrever seu código assim:

lambda args: (expr1, expr2,... exprN)

Em teoria, se você tiver uma avaliação da esquerda para a direita, isso funcionaria, mas você ainda perde valores sendo transmitidos de uma expressão para outra.

Uma maneira de conseguir o que é um pouco mais detalhado é ter

lambda args: [lambda1, lambda2, ..., lambdaN]

Onde cada lambda recebe argumentos do anterior.

def let(*funcs):
    def wrap(args):
        result = args                                                                                                                                                                                                                         
        for func in funcs:
            if not isinstance(result, tuple):
                result = (result,)
            result = func(*result)
        return result
    return wrap

Este método permite que você escreva algo que seja um pouco lisp / esquema.

Então você pode escrever coisas assim:

let(lambda x, y: x+y)((1, 2))

Um método mais complexo pode ser usado para calcular a hipotenusa

lst = [(1,2), (2,3)]
result = map(let(
  lambda x, y: (x**2, y**2),
  lambda x, y: (x + y) ** (1/2)
), lst)

Isso retornará uma lista de números escalares para que possa ser usado para reduzir vários valores para um.

Ter tantos lambda certamente não será muito eficiente, mas se você estiver com restrições, pode ser uma boa maneira de fazer algo rapidamente, depois reescreva-o como uma função real posteriormente.


-3

porque uma função lambda deve ter uma linha, pois é a forma mais simples de uma função, an entrance, then return


1
Não, quem escreve um programa orientado a eventos diz que a lambda multilinha é importante.
Akangka 16/02
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.