E731 não atribua uma expressão lambda, use um def

193

Recebo esse aviso pep8 sempre que uso expressões lambda. As expressões lambda não são recomendadas? Se não, por quê?

Kechit Goyal
fonte
4
Para maior clareza, a pergunta refere-se a uma mensagem para um check-in automático flake8( flake8.pycqa.org )
rakslice

Respostas:

229

A recomendação no PEP-8 em que você está se deparando é:

Sempre use uma declaração def em vez de uma declaração de atribuição que vincule uma expressão lambda diretamente a um nome.

Sim:

def f(x): return 2*x 

Não:

f = lambda x: 2*x 

A primeira forma significa que o nome do objeto de função resultante é especificamente 'f' em vez do genérico '<lambda>'. Isso é mais útil para tracebacks e representações de string em geral. O uso da declaração de atribuição elimina o único benefício que uma expressão lambda pode oferecer sobre uma declaração def explícita (ou seja, que pode ser incorporada a uma expressão maior)

A atribuição de lambdas a nomes basicamente apenas duplica a funcionalidade do def- e, em geral, é melhor fazer algo de uma única maneira para evitar confusão e aumentar a clareza.

O caso de uso legítimo para lambda é onde você deseja usar uma função sem atribuí-la, por exemplo:

sorted(players, key=lambda player: player.rank)

Em geral, o principal argumento contra fazer isso é que as definstruções resultarão em mais linhas de código. Minha principal resposta a isso seria: sim, e tudo bem. A menos que você esteja praticando golfe com código, minimizar o número de linhas não é algo que você deve fazer: vá além do óbvio.

Gareth Latty
fonte
5
Não vejo como é pior. O rastreamento ainda incluirá o número da linha incorreta e o arquivo de origem. Um pode dizer "f" enquanto o outro diz "lambda". Talvez o erro lambda seja mais fácil de verificar, porque não é um nome de função de caractere único ou um nome longo com nome inadequado?
G33kz0r
4
@ g33kz0r Bem, claro, se você assumir que o restante do seu código terá baixa qualidade, seguir as convenções não lhe renderá muito. Em geral, não, não é o fim do mundo, mas ainda é uma má ideia.
precisa
39
Esta resposta não é muito útil porque, ao executar a abordagem sugerida defpelo uso do verificador PEP8, você obtém E704 multiple statements on one line (def)e, se a dividir em duas linhas, obtém E301 expected 1 blank line, found 0: - /
Adam Spiers
4
Eu concordo que deve ser dividido. Meus pontos foram que a) não está dividido no código da resposta acima, causando o E704 eb) se você o dividir, precisará de uma linha em branco feia acima dela para evitar o E301.
Adam Spiers
3
Uso lambdas quando quero enfatizar uma função pura (sem efeitos colaterais), e às vezes tenho que usar a mesma função em dois lugares, ou seja, agrupar e classificar. Então eu ignoro esta convenção.
manu
119

Aqui está a história, eu tinha uma função lambda simples que estava usando duas vezes.

a = map(lambda x : x + offset, simple_list)
b = map(lambda x : x + offset, another_simple_list)

Isto é apenas para a representação, eu já enfrentei duas versões diferentes disso.

Agora, para manter as coisas secas, começo a reutilizar esse lambda comum.

f = lambda x : x + offset
a = map(f, simple_list)
b = map(f, another_simple_list)

Nesse ponto, meu verificador de qualidade de código reclama que lambda é uma função nomeada, então eu a converto em uma função.

def f(x):
    return x + offset
a = map(f, simple_list)
b = map(f, another_simple_list)

Agora, o verificador reclama que uma função deve ser delimitada por uma linha em branco antes e depois.

def f(x):
    return x + offset

a = map(f, simple_list)
b = map(f, another_simple_list)

Aqui temos agora 6 linhas de código, em vez das 2 linhas originais, sem aumento na legibilidade e sem aumento de python. Neste ponto, o verificador de código reclama da função que não possui docstrings.

Na minha opinião, é melhor evitar e quebrar essa regra quando faz sentido, use seu julgamento.

iankit
fonte
13
a = [x + offset for x in simple_list]. Não há necessidade de usar mape lambdaaqui.
21818 Georgy
8
@ Georgia Eu acredito que o objetivo era mover a x + offsetparte para um local abstraído que possa ser atualizado sem alterar mais de uma linha de código. Com as compreensões de lista, como você mencionou, você ainda precisaria de duas linhas de código que continham x + offsetque agora estariam nas compreensões de lista. Para retirá-las como o autor queria, você precisaria de um defou lambda.
Julian
1
@ Julian Além de defe lambdatambém se poderia usar functools.partial : f = partial(operator.add, offset)e então a = list(map(f, simple_list)).
Georgy
Que tal def f(x): return x + offset(isto é, uma função simples definida em uma única linha)? Pelo menos com flake8, não recebo reclamações sobre linhas em branco.
DocOc 22/08/19
1
@Julian Em alguns casos, você pode usar uma compreensão aninhada:a, b = [[x + offset for x lst] for lst in (simple_list, another_simple_list)]
wjandrea
24

O Lattyware está absolutamente certo: basicamente o PEP-8 quer que você evite coisas como

f = lambda x: 2 * x

e use

def f(x):
    return 2 * x

No entanto, conforme abordado em um relatório de bug recente (agosto de 2014), instruções como as seguintes agora são compatíveis:

a.f = lambda x: 2 * x
a["f"] = lambda x: 2 * x

Como meu verificador PEP-8 ainda não implementa isso corretamente, desliguei o E731 por enquanto.

Elmar Peise
fonte
8
Mesmo ao usar def, o verificador PEP8 está em conformidade E301 expected 1 blank line, found 0, então você precisa adicionar uma linha em branco feia antes dele.
Adam Spiers
1

Também encontrei uma situação em que era impossível usar uma função def (ined).

class SomeClass(object):
  # pep-8 does not allow this
  f = lambda x: x + 1  # NOQA

  def not_reachable(self, x):
    return x + 1

  @staticmethod
  def also_not_reachable(x):
    return x + 1

  @classmethod
  def also_not_reachable(cls, x):
    return x + 1

  some_mapping = {
      'object1': {'name': "Object 1", 'func': f},
      'object2': {'name': "Object 2", 'func': some_other_func},
  }

Nesse caso, eu realmente queria fazer um mapeamento que pertencia à classe. Alguns objetos no mapeamento precisavam da mesma função. Seria ilógico colocar a função nomeada fora da classe. Não encontrei uma maneira de me referir a um método (staticmethod, classmethod ou normal) de dentro do corpo da classe. SomeClass ainda não existe quando o código é executado. Portanto, referir-se a ele da classe também não é possível.

Pateta
fonte
Você pode consultar also_not_reachablena definição de mapeamento comoSomeClass.also_not_reachable
yaccz
1
Não sei que argumento você está tentando fazer aqui. Cada um de seus nomes de funções é tão acessível quanto fem 2.7 e 3.5 para mim #
1112 Eric #
Não, todas as funções, exceto a função lambda, não são acessíveis de dentro do corpo da classe. Você obterá um objeto AttributeError: type 'SomeClass' não tem atributo '...' se tentar acessar uma dessas funções no objeto some_mapping.
simP
3
@simP todos eles são perfeitamente acessíveis. Aqueles com @staticmethode @classmethodnão precisam de um objeto, apenas SomeClass.also_not_reachable(embora precisem de nomes distintos). Se você precisar acessá-los a partir de métodos de classe é só usarself.also_not_reachable
ababak
@simP talvez você deva renomear seus *not_reachablemétodos como not_as_easily_reachable_from_class_definition_as_a_lambdaxD
Romain Vincent