O que é mais preferível de usar: funções lambda ou funções aninhadas ('def')?

101

Eu uso principalmente funções lambda, mas às vezes uso funções aninhadas que parecem fornecer o mesmo comportamento.

Aqui estão alguns exemplos triviais em que eles fazem funcionalmente a mesma coisa se um deles for encontrado em outra função:

Função lambda

>>> a = lambda x : 1 + x
>>> a(5)
6

Função aninhada

>>> def b(x): return 1 + x

>>> b(5)
6

Existem vantagens em usar um em relação ao outro? (Desempenho? Legibilidade? Limitações? Consistência? Etc.)

Isso importa mesmo? Se não, isso viola o princípio Pythônico:

Deve haver uma - e de preferência apenas uma - maneira óbvia de fazer isso .

Raio
fonte

Respostas:

105

Se você precisar atribuir o lambdaa um nome, use um def. defs são apenas açúcar sintático para uma atribuição, então o resultado é o mesmo e eles são muito mais flexíveis e legíveis.

lambdas pode ser usado para uso uma vez, jogue fora as funções que não terão um nome.

No entanto, esse caso de uso é muito raro. Você raramente precisa passar objetos de função sem nome.

Os objetos de função embutidos map()e filter()precisam, mas compreensões de lista e expressões geradoras são geralmente mais legíveis do que essas funções e podem abranger todos os casos de uso, sem a necessidade de lambdas.

Para os casos em que você realmente precisa de um pequeno objeto de função, você deve usar as operatorfunções do módulo, como em operator.addvez delambda x, y: x + y

Se você ainda precisar de alguns lambdanão abrangidos, pode considerar escrever um def, apenas para ser mais legível. Se a função for mais complexa do que as do operatormódulo, defprovavelmente a é melhor.

Portanto, bons lambdacasos de uso do mundo real são muito raros.

nosklo
fonte
9
Concordo com a resposta de quando usar lambda, mas discordo que isso é "muito raro", é comum para funções-chave para sortedou itertools.groupbyetc., por exemplosorted(['a1', 'b0'], key= lambda x: int(x[1]))
Chris_Rands
30

Na prática, para mim, existem duas diferenças:

A primeira é sobre o que eles fazem e o que eles retornam:

  • def é uma palavra-chave que não retorna nada e cria um 'nome' no namespace local.

  • lambda é uma palavra-chave que retorna um objeto de função e não cria um 'nome' no namespace local.

Portanto, se você precisar chamar uma função que recebe um objeto de função, a única maneira de fazer isso em uma linha de código python é com um lambda. Não há equivalente com def.

Em algumas estruturas, isso é bastante comum; por exemplo, eu uso muito o Twisted e faço algo como

d.addCallback(lambda result: setattr(self, _someVariable, result))

é bastante comum e mais conciso com lambdas.

A segunda diferença é sobre o que a função real pode fazer.

  • Uma função definida com 'def' pode conter qualquer código Python
  • Uma função definida com 'lambda' deve ser avaliada como uma expressão e, portanto, não pode conter instruções como imprimir, importar, aumentar, ...

Por exemplo,

def p(x): print x

funciona como esperado, enquanto

lambda x: print x

é um SyntaxError.

Claro, existem soluções alternativas - substitua printcom sys.stdout.write, ou importcom __import__. Mas geralmente é melhor você ir com uma função nesse caso.

Thomas Vander Stichele
fonte
22

Nesta entrevista, Guido van Rossum diz que gostaria de não ter deixado 'lambda' em Python:

" P. Com qual recurso do Python você está menos satisfeito?

Às vezes, tenho sido muito rápido em aceitar contribuições e depois percebi que era um erro. Um exemplo seriam alguns dos recursos de programação funcional, como funções lambda. Lambda é uma palavra-chave que permite criar uma pequena função anônima; funções integradas, como mapear, filtrar e reduzir, executam uma função em um tipo de sequência, como uma lista.

Na prática, não deu muito certo. Python tem apenas dois escopos: local e global. Isso torna a escrita de funções lambda difícil, porque geralmente você deseja acessar variáveis ​​no escopo onde o lambda foi definido, mas não pode por causa dos dois escopos. Há uma maneira de contornar isso, mas é uma espécie de kludge. Freqüentemente, parece muito mais fácil em Python usar apenas um loop for em vez de brincar com funções lambda. mapa e amigos funcionam bem apenas quando já existe uma função integrada que faz o que você deseja.

IMHO, Iambdas pode ser conveniente às vezes, mas geralmente são convenientes em detrimento da legibilidade. Você pode me dizer o que isso faz:

str(reduce(lambda x,y:x+y,map(lambda x:x**x,range(1,1001))))[-10:]

Eu escrevi e levei um minuto para descobrir. Isto é do Projeto Euler - não direi qual é o problema porque odeio spoilers, mas ele roda em 0,124 segundos :)

Chris Lawlor
fonte
20
Observe que a entrevista é bastante antiga, e Python há muito adicionou escopos aninhados, o que torna o argumento que ele dá contra lambda não mais relevante. Tenho certeza de que ele ainda se arrepende de lambda, mas não o suficiente para removê-lo no Python 3.0.
Thomas Wouters
10
Na verdade, o seu exemplo deve ser um argumento contra frases simples, não lambdas. Além disso, você deve ter usado a função de soma integrada em vez de reduzir com lambda: str (sum (map (lambda x: x ** x, intervalo (1001)))) [: - 10]
Tríptico
2
@ThomasWouters: Eu entendo que lambdanão ser removido no 3.0 era algo próximo, e que Guido não estava lutando para mantê-lo.
Ethan Furman
11

Para n = 1000, aqui está algum tempo para chamar uma função versus um lambda:

In [11]: def f(a, b):
             return a * b

In [12]: g = lambda x, y: x * y

In [13]: %%timeit -n 100
for a in xrange(n):
  for b in xrange(n):
    f(a, b)
   ....:
100 loops, best of 3: 285 ms per loop

In [14]: %%timeit -n 100
for a in xrange(n):
  for b in xrange(n):
    g(a, b)
   ....:
100 loops, best of 3: 298 ms per loop

In [15]: %%timeit -n 100
for a in xrange(n):
  for b in xrange(n):
    (lambda x, y: x * y)(a, b)
   ....:
100 loops, best of 3: 462 ms per loop
Andy Hayden
fonte
3
Interessante ver que o lambda e as versões definidas são aproximadamente equivalentes. O último teste demorou mais porque o python provavelmente precisou alocar espaço toda vez que definiu a função lambda.
hlin117 de
Eu acho que isso faz sentido, pois a definição pode fazer referência a variáveis ​​locais (que podem ter mudado) ... embora no caso em que não faz, como aqui, cpython poderia fazer um trabalho melhor.
Andy Hayden de
Use dis.dis; Seu (lambda x, y: x * y) cria a função a cada loop. Se você criar o lambda antes do loop (também conhecido como f = lambda x, y: x * y), o bytecode para chamar a função será exatamente o mesmo que g / f no exemplo anterior, portanto, o desempenho do lambda é o mesmo como uma função def. Portanto, lambda ou def como nenhum impacto se você usá-lo da mesma forma. Faça o inverso, declare a função f () dentro do loop e chame-a de ...
tito
@tito Eu acredito que é precisamente o que os 3 exemplos cronometrados demonstram ...
Andy Hayden
@tito oh, você está dizendo definir a função no loop, com certeza, mas eu diria que é um padrão incomum. Não sei por que isso precisava de um voto negativo naquele comentário ...
Andy Hayden
7

Atuação:

Criar uma função com lambdaé um pouco mais rápido do que criá-la com def. A diferença se deve à defcriação de uma entrada de nome na tabela de locais. A função resultante tem a mesma velocidade de execução.


Legibilidade:

As funções Lambda são um pouco menos legíveis para a maioria dos usuários de Python, mas também muito mais concisas em algumas circunstâncias. Considere a conversão de uma rotina não funcional para uma rotina funcional:

# Using non-functional version.

heading(math.sqrt(v.x * v.x + v.y * v.y), math.atan(v.y / v.x))

# Using lambda with functional version.

fheading(v, lambda v: math.sqrt(v.x * v.x + v.y * v.y), lambda v: math.atan(v.y / v.x))

# Using def with functional version.

def size(v):
    return math.sqrt(v.x * v.x + v.y * v.y)

def direction(v):
    return math.atan(v.y / v.x)

deal_with_headings(v, size, direction)

Como você pode ver, a lambdaversão é mais curta e "mais fácil" no sentido de que você só precisa adicionar lambda v:à versão não funcional original para converter para a versão funcional. Também é muito mais conciso. Mas lembre-se, muitos usuários de Python ficarão confusos com a sintaxe lambda, então o que você perde em comprimento e complexidade real pode ser recuperado na confusão de outros desenvolvedores.


Limitações:

  • lambda as funções só podem ser usadas uma vez, a menos que sejam atribuídas a um nome de variável.
  • lambdafunções atribuídas a nomes de variáveis ​​não têm vantagem sobre deffunções.
  • lambda funções podem ser difíceis ou impossíveis de conservar.
  • def os nomes das funções devem ser cuidadosamente escolhidos para serem razoavelmente descritivos e únicos ou, pelo menos, não utilizados em seu escopo.

Consistência:

Python principalmente evita convenções de programação funcional em favor de semânticas objetivas mais simples e procedurais. O lambdaoperador está em contraste direto com esse viés. Além disso, como uma alternativa ao já predominante def, a lambdafunção adiciona diversidade à sua sintaxe. Alguns considerariam isso menos consistente.


Funções pré-existentes:

Conforme observado por outros, muitos usos de lambdano campo podem ser substituídos por membros do operatorou de outros módulos. Por exemplo:

do_something(x, y, lambda x, y: x + y)
do_something(x, y, operator.add)

O uso da função pré-existente pode tornar o código mais legível em muitos casos.


O princípio pitônico: "Deve haver uma - e de preferência apenas uma - maneira óbvia de fazer isso"

Isso é semelhante à doutrina da fonte única da verdade . Infelizmente, o princípio da única maneira óbvia de fazer isso sempre foi mais uma aspiração melancólica para Python, em vez de um verdadeiro princípio orientador. Considere as abrangências de array muito poderosas em Python. Eles são funcionalmente equivalentes às funções mape filter:

[e for e in some_array if some_condition(e)]
filter(some_array, some_condition)

lambdae defsão iguais.

É uma questão de opinião, mas eu diria que qualquer coisa na linguagem Python destinada ao uso geral que obviamente não quebra nada é "Pythônico" o suficiente.

Pi Marillion
fonte
7

Mais preferível: funções lambda ou funções aninhadas ( def)?

Há uma vantagem em usar um lambda em vez de uma função regular: eles são criados em uma expressão.

Existem várias desvantagens:

  • sem nome (apenas '<lambda>')
  • sem docstrings
  • sem anotações
  • sem declarações complexas

Eles também são o mesmo tipo de objeto. Por esses motivos, geralmente prefiro criar funções com a defpalavra - chave em vez de lambdas.

Primeiro ponto - eles são o mesmo tipo de objeto

Um lambda resulta no mesmo tipo de objeto que uma função regular

>>> l = lambda: 0
>>> type(l)
<class 'function'>
>>> def foo(): return 0
... 
>>> type(foo)
<class 'function'>
>>> type(foo) is type(l)
True

Como lambdas são funções, eles são objetos de primeira classe.

Lambdas e funções:

  • pode ser transmitido como um argumento (o mesmo que uma função regular)
  • quando criado dentro de uma função externa, torna-se um fechamento sobre os locais das funções externas

Mas lambdas estão, por padrão, perdendo algumas coisas que as funções obtêm por meio da sintaxe de definição de função completa.

Um lamba __name__é'<lambda>'

Afinal, lambdas são funções anônimas, então eles não sabem o próprio nome.

>>> l.__name__
'<lambda>'
>>> foo.__name__
'foo'

Portanto, lambda's não podem ser pesquisados ​​programaticamente em seu namespace.

Isso limita certas coisas. Por exemplo, foopode ser pesquisado com código serializado, enquanto lnão pode:

>>> import pickle
>>> pickle.loads(pickle.dumps(l))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
_pickle.PicklingError: Can't pickle <function <lambda> at 0x7fbbc0464e18>: 
attribute lookup <lambda> on __main__ failed

Podemos pesquisar foomuito bem - porque ele conhece seu próprio nome:

>>> pickle.loads(pickle.dumps(foo))
<function foo at 0x7fbbbee79268>

Lambdas não têm anotações nem docstring

Basicamente, lambdas não são documentados. Vamos reescrever foopara ser melhor documentado:

def foo() -> int:
    """a nullary function, returns 0 every time"""
    return 0

Agora, foo tem documentação:

>>> foo.__annotations__
{'return': <class 'int'>}
>>> help(foo)
Help on function foo in module __main__:

foo() -> int
    a nullary function, returns 0 every time

Considerando que, não temos o mesmo mecanismo para fornecer as mesmas informações para lambdas:

>>> help(l)
Help on function <lambda> in module __main__:

<lambda> lambda (...)

Mas podemos hackea-los:

>>> l.__doc__ = 'nullary -> 0'
>>> l.__annotations__ = {'return': int}
>>> help(l)
Help on function <lambda> in module __main__:

<lambda> lambda ) -> in
    nullary -> 0

Porém, provavelmente há algum erro atrapalhando a saída da ajuda.

Lambdas só pode retornar uma expressão

Lambdas não podem retornar declarações complexas, apenas expressões.

>>> lambda: if True: 0
  File "<stdin>", line 1
    lambda: if True: 0
             ^
SyntaxError: invalid syntax

As expressões podem ser bastante complexas e, se você tentar muito, provavelmente conseguirá o mesmo com um lambda, mas a complexidade adicionada é mais prejudicial à escrita de código claro.

Usamos Python para maior clareza e facilidade de manutenção. O uso excessivo de lambdas pode funcionar contra isso.

A única vantagem para lambdas: pode ser criado em uma única expressão

Esta é a única vantagem possível. Já que você pode criar um lambda com uma expressão, você pode criá-lo dentro de uma chamada de função.

Criar uma função dentro de uma chamada de função evita a pesquisa de nome (barata) em comparação com uma criada em outro lugar.

No entanto, como o Python é avaliado estritamente, não há outro ganho de desempenho em fazer isso além de evitar a consulta de nome.

Para uma expressão muito simples, posso escolher um lambda.

Eu também tendo a usar lambdas ao fazer Python interativo, para evitar várias linhas quando uma delas faz. Eu uso o seguinte tipo de formato de código quando quero passar um argumento para um construtor ao chamar timeit.repeat:

import timeit

def return_nullary_lambda(return_value=0):
    return lambda: return_value

def return_nullary_function(return_value=0):
    def nullary_fn():
        return return_value
    return nullary_fn

E agora:

>>> min(timeit.repeat(lambda: return_nullary_lambda(1)))
0.24312214995734394
>>> min(timeit.repeat(lambda: return_nullary_function(1)))
0.24894469301216304

Acredito que a ligeira diferença de tempo acima pode ser atribuída à pesquisa de nome em return_nullary_function- observe que é muito insignificante.

Conclusão

Lambdas são bons para situações informais em que você deseja minimizar linhas de código em favor de fazer um ponto singular.

Lambdas são ruins para situações mais formais em que você precisa de clareza para editores de código que virão mais tarde, especialmente nos casos em que não são triviais.

Sabemos que devemos dar bons nomes aos nossos objetos. Como podemos fazer isso quando o objeto não tem nome?

Por todos esses motivos, geralmente prefiro criar funções com em defvez de com lambda.

Aaron Hall
fonte
6

Eu concordo com o conselho de nosklo: se você precisar dar um nome à função, use def. Reservo lambdafunções para casos em que estou apenas passando um breve trecho de código para outra função, por exemplo:

a = [ (1,2), (3,4), (5,6) ]
b = map( lambda x: x[0]+x[1], a )
Dan Lenski
fonte
3
Na maioria das combinações de map / lambda, você pode substituí-lo por uma compreensão de lista ou função mais apropriada. Por exemplo, "map (sum, a)" ou "[x [0] + x [1] para x em a]"
John Millikin
Sim, é verdade. Às vezes eu prefiro map () embora. Este foi principalmente apenas um exemplo inventado de usar uma função em linha.
Dan Lenski,
exatamente ... A maioria dos exemplos são inventados, porque não é natural de usar e há maneiras práticas melhores na maioria dos casos.
nosklo
5

Embora concorde com as outras respostas, às vezes é mais legível. Aqui está um exemplo que lambdaé útil, em um caso de uso que sempre encontro de N dimensional defaultdict.
Aqui está um exemplo:

from collections import defaultdict
d = defaultdict(lambda: defaultdict(list))
d['Foo']['Bar'].append(something)

Acho mais legível do que criar um defpara a segunda dimensão. Isso é ainda mais significativo para dimensões superiores.

Jonathan
fonte
from functools import partial; defaultdict(partial(defaultdict, list)). Atribua parcial a um nome se quiser usá-lo mais de uma vez. Mas, se você continuar encontrando essa construção, significa que você não é SECO. Fatore em uma biblioteca de utilitários. Você pode usar essa construção para criar um defaultdict arbitrário n-dimensional usando outras functools (ou um loop ou recursão).
DylanYoung
3

O uso principal de lambda sempre foi para funções de retorno de chamada simples e para mapear, reduzir, filtrar, que requerem uma função como argumento. Com as compreensões de lista se tornando a norma, e o acréscimo permitido se como em:

x = [f for f in range(1, 40) if f % 2]

é difícil imaginar um caso real para o uso de lambda no uso diário. Como resultado, eu diria, evite lambda e crie funções aninhadas.

apg
fonte
3

Uma limitação importante dos lambdas é que eles não podem conter nada além de uma expressão. É quase impossível para uma expressão lambda produzir qualquer coisa além de efeitos colaterais triviais, uma vez que ela não pode ter um corpo tão rico quanto uma deffunção 'ed.

Dito isso, Lua influenciou meu estilo de programação no sentido do uso extensivo de funções anônimas e eu amontoei meu código com elas. Além disso, tendo a pensar em mapear / reduzir como operadores abstratos de maneiras que não considero compreensões de lista ou geradores, quase como se estivesse adiando uma decisão de implementação explicitamente usando esses operadores.

Edit: Esta é uma questão muito antiga, e minhas opiniões sobre o assunto mudaram um pouco.

Em primeiro lugar, sou fortemente contra atribuir uma lambdaexpressão a uma variável; já que python tem uma sintaxe especial apenas para isso (dica, def). Além disso, muitos dos usos de lambda, mesmo quando não recebem um nome, têm implementações predefinidas (e mais eficientes). Por exemplo, o exemplo em questão pode ser abreviado para apenas (1).__add__, sem a necessidade de envolvê-lo em lambdaou def. Muitos outros usos comuns pode ser satisfeita com alguma combinação das operator, itertoolse functoolsmódulos.

SingleNegationElimination
fonte
1
(1).__add__- chamar métodos dunder diretamente quase nunca deve acontecer. Mil lambdas para cada chamada direta de Dunder.
Ethan Furman
1
@ EthanFurman: Bem, na minha experiência, chamadas da natureza (1).__add__são um tanto incomuns, mas eu não chegaria perto de "deveria". sem dúvida, acho o primeiro muito mais legível lambda x: 1 + x. Se tivéssemos algo mais parecido com a notação de fatia haskells, (1+)isso seria ótimo, mas temos que nos contentar com o que é semanticamente exatamente essa coisa, o nome do método dunder.
SingleNegationElimination
2
  • Tempo de computação.
  • Função sem nome.
  • Para alcançar uma função e muitas funções de uso.

Considerando um exemplo simples,

# CREATE ONE FUNCTION AND USE IT TO PERFORM MANY OPERATIONS ON SAME TYPE OF DATA STRUCTURE.
def variousUse(a,b=lambda x:x[0]):
    return [b(i) for i in a]

dummyList = [(0,1,2,3),(4,5,6,7),(78,45,23,43)]
variousUse(dummyList)                           # extract first element
variousUse(dummyList,lambda x:[x[0],x[2],x[3]]) # extract specific indexed element
variousUse(dummyList,lambda x:x[0]+x[2])        # add specific elements
variousUse(dummyList,lambda x:x[0]*x[2])        # multiply specific elements
bhargav patel
fonte
1

Se você for apenas atribuir o lambda a uma variável no escopo local, você também pode usar def porque é mais legível e pode ser expandido mais facilmente no futuro:

fun = lambda a, b: a ** b # a pointless use of lambda
map(fun, someList)

ou

def fun(a, b): return a ** b # more readable
map(fun, someList)
muito php
fonte
Ambos from operator import pow;map(pow, someList)e (a**b for a,b in someList)são ainda mais legíveis.
InQβ
1

Um uso para lambdas que encontrei ... é em mensagens de depuração.

Como lambdas podem ser avaliados lentamente, você pode ter um código como este:

log.debug(lambda: "this is my message: %r" % (some_data,))

em vez de possivelmente caro:

log.debug("this is my message: %r" % (some_data,))

que processa a string de formato mesmo se a chamada de depuração não produzir saída devido ao nível de registro atual.

Obviamente, para que funcione conforme descrito, o módulo de registro em uso deve suportar lambdas como "parâmetros preguiçosos" (como meu módulo de registro faz).

A mesma ideia pode ser aplicada a qualquer outro caso de avaliação preguiçosa para criação de valor de conteúdo on demand.

Por exemplo, este operador ternário personalizado:

def mif(condition, when_true, when_false):
    if condition:
         return when_true()
    else:
         return when_false()

mif(a < b, lambda: a + a, lambda: b + b)

ao invés de:

def mif(condition, when_true, when_false):
    if condition:
         return when_true
    else:
         return when_false

mif(a < b, a + a, b + b)

com lambdas apenas a expressão selecionada pela condição será avaliada, sem lambdas ambos serão avaliados.

É claro que você poderia simplesmente usar funções em vez de lambdas, mas para expressões curtas lambdas são (c) mais enxutos.

Glushiator
fonte
1
NB loggingjá tem formatação lenta: log.debug("this is my message: %r", some_data)só formatará quando / se a mensagem for solicitada.
j08lue
O método @ j08lue lambda ignora a avaliação de tudo caso a saída de depuração não seja produzida, caso você mostre que some_datapode ser uma expressão cara ou chamada de função / método.
Glushiator
0

Eu concordo com nosklo. A propósito, mesmo com uma função use uma vez, jogue fora , na maioria das vezes você quer apenas usar algo do módulo de operação.

POR EXEMPLO :

Você tem uma função com esta assinatura: myFunction (data, callback function).

Você quer passar uma função que adiciona 2 elementos.

Usando lambda:

myFunction(data, (lambda x, y : x + y))

A maneira pítônica:

import operator
myFunction(data, operator.add)

Ou, claro, este é um exemplo simples, mas há muitas coisas que o módulo do operador fornece, incluindo os itens setters / getters para list e dict. Realmente legal.

e-satis
fonte
-1

A principal diferença é que você não pode usar deffunções embutidas, o que é, em minha opinião, o caso de uso mais conveniente para uma lambdafunção. Por exemplo, ao classificar uma lista de objetos:

my_list.sort(key=lambda o: o.x)

Eu sugeriria, portanto, manter o uso de lambdas para esse tipo de operação trivial, que também não se beneficia muito da documentação automática fornecida ao nomear a função.

Ali Rasim Kocal
fonte
-2

lambda é útil para gerar novas funções:

>>> def somefunc(x): return lambda y: x+y
>>> f = somefunc(10)
>>> f(2)
12
>>> f(4)
14
scrpy
fonte