E731 n'attribue pas d'expression lambda, utilise une def

193

J'obtiens cet avertissement pep8 chaque fois que j'utilise des expressions lambda. Les expressions lambda ne sont-elles pas recommandées? Sinon pourquoi?

Kechit Goyal
la source
4
Pour plus de clarté, la question fait référence à un message pour un enregistrement automatique flake8( flake8.pycqa.org )
rakslice

Réponses:

232

La recommandation dans PEP-8 que vous rencontrez est:

Utilisez toujours une instruction def au lieu d'une instruction d'affectation qui lie une expression lambda directement à un nom.

Oui:

def f(x): return 2*x 

Non:

f = lambda x: 2*x 

La première forme signifie que le nom de l'objet fonction résultant est spécifiquement «f» au lieu du générique «<lambda>». Ceci est plus utile pour les traces et les représentations sous forme de chaîne en général. L'utilisation de l'instruction d'affectation élimine le seul avantage qu'une expression lambda peut offrir par rapport à une instruction def explicite (c'est-à-dire qu'elle peut être intégrée dans une expression plus grande)

L'attribution de lambdas à des noms ne fait que dupliquer la fonctionnalité de def - et en général, il est préférable de faire quelque chose d'une seule manière pour éviter la confusion et augmenter la clarté.

Le cas d'utilisation légitime de lambda est celui où vous souhaitez utiliser une fonction sans l'affecter, par exemple:

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

En général, le principal argument contre cela est que les definstructions entraîneront plus de lignes de code. Ma principale réponse à cela serait: oui, et c'est très bien. À moins que vous ne codiez, minimiser le nombre de lignes n'est pas quelque chose que vous devriez faire: optez pour le clair plutôt que pour le court.

Gareth Latty
la source
5
Je ne vois pas comment c'est pire. Le traçage va toujours inclure le numéro de ligne errant et le fichier source. L'un pourrait dire «f» tandis que l'autre dit «lambda». Peut-être que l'erreur lambda est plus facile à analyser car ce n'est pas un nom de fonction à un seul caractère ou un nom long mal nommé?
g33kz0r
4
@ g33kz0r Eh bien, bien sûr, si vous supposez que le reste de votre code sera de mauvaise qualité, suivre les conventions ne vous rapportera pas beaucoup. En général, non, ce n'est pas la fin du monde, mais c'est toujours une mauvaise idée.
Gareth Latty
40
Cette réponse n'est pas très utile, car lors de l'exécution de l'approche suggérée d'utilisation defvia le vérificateur PEP8, vous obtenez E704 multiple statements on one line (def), et si vous la divisez en deux lignes, vous obtenez E301 expected 1 blank line, found 0: - /
Adam Spiers
4
Je suis d'accord qu'il devrait être divisé. Mes points étaient que a) il n'est pas divisé dans le code de la réponse ci-dessus, provoquant E704, et b) si vous le divisez, vous avez besoin d'une ligne vierge laide au-dessus pour éviter E301.
Adam Spiers
3
J'utilise des lambdas lorsque je veux mettre l'accent sur une fonction pure (pas d'effets secondaires), et parfois je dois utiliser la même fonction à deux endroits, c'est-à-dire groupby et trier ensemble. Alors j'ignore cette convention.
manu
120

Voici l'histoire, j'avais une simple fonction lambda que j'utilisais deux fois.

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

C'est juste pour la représentation, j'ai fait face à deux versions différentes de cela.

Maintenant, pour garder les choses SEC, je commence à réutiliser ce lambda commun.

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

À ce stade, mon vérificateur de qualité de code se plaint du fait que lambda est une fonction nommée, je la convertis donc en fonction.

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

Maintenant, le vérificateur se plaint qu'une fonction doit être délimitée par une ligne vierge avant et après.

def f(x):
    return x + offset

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

Ici, nous avons maintenant 6 lignes de code au lieu de 2 lignes originales sans augmentation de la lisibilité et sans augmentation du caractère pythonique. À ce stade, le vérificateur de code se plaint que la fonction n'a pas de docstrings.

À mon avis, il vaut mieux éviter et enfreindre cette règle lorsque cela a du sens, utilisez votre jugement.

iankit
la source
13
a = [x + offset for x in simple_list]. Pas besoin d'utiliser mapet lambdaici.
Georgy
9
@Georgy Je crois que le but était de déplacer la x + offsetpartie vers un emplacement abstrait qui peut être mis à jour sans changer plus d'une ligne de code. Avec les compréhensions de liste comme vous l'avez mentionné, vous auriez toujours besoin de deux lignes de code qui les contiendraient x + offsetjuste maintenant dans les compréhensions de liste. Afin de les extraire comme l'auteur le voulait, vous auriez besoin d'un fichier defou lambda.
Julian
1
@Julian En dehors de defet lambdaon pourrait aussi utiliser functools.partial : f = partial(operator.add, offset)et puis a = list(map(f, simple_list)).
Georgy
Qu'en est-il def f(x): return x + offset(c'est-à-dire une fonction simple définie sur une seule ligne)? Au moins avec flake8, je ne reçois pas de plaintes concernant les lignes vides.
DocOc
1
@Julian Dans certains cas, vous pouvez utiliser une compréhension imbriquée:a, b = [[x + offset for x lst] for lst in (simple_list, another_simple_list)]
wjandrea
24

Lattyware a tout à fait raison: fondamentalement, PEP-8 veut que vous évitiez des choses comme

f = lambda x: 2 * x

et utilisez plutôt

def f(x):
    return 2 * x

Cependant, comme indiqué dans un récent rapport de bogue (août 2014), des déclarations telles que les suivantes sont désormais conformes:

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

Étant donné que mon vérificateur PEP-8 ne l'implémente pas encore correctement, j'ai désactivé E731 pour le moment.

Elmar Peise
la source
8
Même lors de l'utilisation def, le vérificateur PEP8 se plaint E301 expected 1 blank line, found 0, vous devez donc ajouter une ligne vierge laide avant.
Adam Spiers
1

J'ai également rencontré une situation dans laquelle il était même impossible d'utiliser une fonction 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},
  }

Dans ce cas, je voulais vraiment faire un mapping qui appartenait à la classe. Certains objets du mappage nécessitaient la même fonction. Il serait illogique de placer la fonction nommée en dehors de la classe. Je n'ai pas trouvé de moyen de faire référence à une méthode (méthode statique, méthode de classe ou normale) depuis l'intérieur du corps de la classe. SomeClass n'existe pas encore lorsque le code est exécuté. Il n'est donc pas non plus possible de s'y référer depuis la classe.

simP
la source
Vous pouvez faire référence also_not_reachabledans la définition de mappage commeSomeClass.also_not_reachable
yaccz
1
Je ne sais pas quel point vous essayez de faire valoir ici. Chacun de vos noms de fonction est aussi accessible que fdans les versions 2.7 et 3.5 pour moi
Eric
Non, toutes les fonctions, à l'exception de la fonction lambda, ne sont pas accessibles à partir du corps de la classe. Vous obtiendrez une AttributeError: l'objet de type 'SomeClass' n'a pas d'attribut '...' si vous essayez d'accéder à l'une de ces fonctions dans l'objet some_mapping.
simP
3
@simP tous sont parfaitement accessibles. Ceux avec @staticmethodet @classmethodn'ont pas besoin d'un objet, juste SomeClass.also_not_reachable(bien qu'ils aient besoin de noms distinctifs). Si vous avez besoin d'y accéder à partir de méthodes de classe, utilisez simplementself.also_not_reachable
ababak
@simP peut-être que vous devriez renommer vos *not_reachableméthodes en not_as_easily_reachable_from_class_definition_as_a_lambdaxD
Romain Vincent