E731 no asigne una expresión lambda, use un def

193

Recibo esta advertencia pep8 cada vez que uso expresiones lambda. ¿No se recomiendan las expresiones lambda? Si no, ¿por qué?

Kechit Goyal
fuente
44
Para mayor claridad, la pregunta se refiere a un mensaje para una verificación automática flake8( flake8.pycqa.org )
rakslice

Respuestas:

229

La recomendación en PEP-8 con la que se encuentra es:

Utilice siempre una instrucción def en lugar de una instrucción de asignación que vincule una expresión lambda directamente a un nombre.

Si:

def f(x): return 2*x 

No:

f = lambda x: 2*x 

La primera forma significa que el nombre del objeto de función resultante es específicamente 'f' en lugar del genérico '<lambda>'. Esto es más útil para trazas y representaciones de cadenas en general. El uso de la declaración de asignación elimina el único beneficio que puede ofrecer una expresión lambda sobre una declaración def explícita (es decir, que se puede incrustar dentro de una expresión más grande)

Asignar lambdas a nombres básicamente duplica la funcionalidad de def, y en general, es mejor hacer algo de una sola manera para evitar confusiones y aumentar la claridad.

El caso de uso legítimo para lambda es donde desea utilizar una función sin asignarla, por ejemplo:

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

En general, el argumento principal en contra de hacer esto es que las defdeclaraciones generarán más líneas de código. Mi respuesta principal a eso sería: sí, y eso está bien. A menos que esté jugando golf de códigos, minimizar el número de líneas no es algo que deba hacer: vaya para despejar en corto.

Gareth Latty
fuente
55
No veo cómo es peor. El rastreo aún incluirá el número de línea errante y el archivo fuente. Uno podría decir "f" mientras que el otro dice "lambda". ¿Quizás el error lambda es más fácil de escanear porque no es un nombre de función de un solo carácter o un nombre largo mal nombrado?
g33kz0r
44
@ g33kz0r Bueno, claro, si asumes que el resto de tu código tendrá mala calidad, seguir las convenciones no te hará ganar mucho. En general, no, no es el fin del mundo, pero sigue siendo una mala idea.
Gareth Latty
39
Esta respuesta no es muy útil, porque al ejecutar el enfoque sugerido de usar a deftravés del verificador PEP8, obtienes E704 multiple statements on one line (def), y si lo divides en dos líneas obtienes E301 expected 1 blank line, found 0: - /
Adam Spires
44
Estoy de acuerdo en que debería dividirse. Mis puntos fueron que a) no está dividido en el código de respuesta anterior, causando E704, yb) si lo divide, necesita una línea fea en blanco encima para evitar E301.
Adam Spires
3
Uso lambdas cuando quiero enfatizar una función pura (sin efectos secundarios), y a veces tengo que usar la misma función en dos lugares, es decir, agrupar y ordenar juntos. Así que ignoro esta convención.
manu
119

Aquí está la historia, tenía una función lambda simple que estaba usando dos veces.

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

Esto es solo para la representación, me he enfrentado a un par de versiones diferentes de esto.

Ahora, para mantener las cosas SECAS, empiezo a reutilizar esta lambda común.

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

En este punto, mi verificador de calidad de código se queja de que lambda es una función con nombre, por lo que la convierto en una función.

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

Ahora el verificador se queja de que una función tiene que estar delimitada por una línea en blanco antes y después.

def f(x):
    return x + offset

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

Aquí tenemos ahora 6 líneas de código en lugar de las 2 líneas originales sin aumento en la legibilidad y sin aumento en ser pitónico. En este punto, el verificador de código se queja de que la función no tiene cadenas de documentos.

En mi opinión, es mejor evitar y romper esta regla cuando tenga sentido, use su criterio.

iankit
fuente
13
a = [x + offset for x in simple_list]. No es necesario usar mapy lambdaaquí.
Georgy
8
@Georgy Creo que el punto era mover la x + offsetporción a una ubicación abstracta que se puede actualizar sin cambiar más de una línea de código. Con las comprensiones de listas como mencionó, aún necesitaría dos líneas de código que contuvieran x + offset, ahora estarían en comprensiones de listas. Para sacarlos como el autor quería, necesitaría una defo lambda.
Julian
1
@Julian Aparte de defy lambdauno también podría usar functools.partial : f = partial(operator.add, offset)y luego a = list(map(f, simple_list)).
Georgy
Qué pasa def f(x): return x + offset (es decir, una función simple definida en una sola línea)? Al menos con flake8 no recibo quejas sobre líneas en blanco.
DocOc
1
@Julian En algunos casos puedes usar una comprensión anidada:a, b = [[x + offset for x lst] for lst in (simple_list, another_simple_list)]
wjandrea
24

Lattyware tiene toda la razón: básicamente, PEP-8 quiere que evites cosas como

f = lambda x: 2 * x

y en su lugar usar

def f(x):
    return 2 * x

Sin embargo, como se abordó en un informe reciente de errores (agosto de 2014), declaraciones como las siguientes ahora son compatibles:

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

Como mi corrector PEP-8 todavía no lo implementa correctamente, apagué E731 por el momento.

Elmar Peise
fuente
8
Incluso cuando se usa def, el corrector PEP8 se queja E301 expected 1 blank line, found 0, por lo que debe agregar una línea en blanco fea antes.
Adam Spires
1

También me encontré con una situación en la que incluso era imposible usar una función 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},
  }

En este caso, realmente quería hacer un mapeo que perteneciera a la clase. Algunos objetos en el mapeo necesitaban la misma función. Sería ilógico colocar una función con nombre fuera de la clase. No he encontrado una manera de referirme a un método (método estático, método de clase o normal) desde el interior del cuerpo de la clase. SomeClass aún no existe cuando se ejecuta el código. Entonces, referirse a él desde la clase tampoco es posible.

tonto como un asno
fuente
Puede referirse also_not_reachableen la definición de mapeo comoSomeClass.also_not_reachable
yaccz
1
No sé qué punto estás tratando de hacer aquí. Cada uno de los nombres de sus funciones es tan accesible como fen 2.7 y 3.5 para mí
Eric
No, no se puede acceder a todas las funciones, excepto la función lambda, desde el cuerpo de la Clase. Obtendrá un atributo AttributeError: type 'SomeClass' no tiene atributo '...' si intenta acceder a una de esas funciones en el objeto some_mapping.
simP
3
@simP todos ellos son perfectamente accesibles. Los que tienen @staticmethody @classmethodno necesitan un objeto, solo SomeClass.also_not_reachable(aunque necesitan nombres distintivos). Si necesita acceder a ellos desde los métodos de clase, simplemente useself.also_not_reachable
ababak
@simP tal vez deberías cambiar el nombre de tus *not_reachablemétodos como not_as_easily_reachable_from_class_definition_as_a_lambdaxD
Romain Vincent