Pourquoi l'utilisation de len (SEQUENCE) dans les valeurs de condition est-elle considérée incorrecte par Pylint?

211

Considérant cet extrait de code:

from os import walk

files = []
for (dirpath, _, filenames) in walk(mydir):
    # more code that modifies files
if len(files) == 0: # <-- C1801
    return None

J'ai été alarmé par Pylint avec ce message concernant la ligne avec l'instruction if:

[pylint] C1801: Ne pas utiliser len(SEQUENCE)comme valeur de condition

La règle C1801, à première vue, ne me semblait pas très raisonnable et la définition du guide de référence n'explique pas pourquoi c'est un problème. En fait, il l'appelle carrément une utilisation incorrecte .

len-as-condition (C1801) : Ne pas utiliser len(SEQUENCE)comme valeur de condition Utilisé lorsque Pylint détecte une utilisation incorrecte de len (séquence) à l'intérieur des conditions.

Mes tentatives de recherche n'ont pas réussi non plus à me fournir une explication plus approfondie. Je comprends que la propriété de longueur d'une séquence peut être évaluée paresseusement, et cela __len__peut être programmé pour avoir des effets secondaires, mais il est douteux que cela seul soit suffisamment problématique pour que Pylint appelle une telle utilisation incorrecte. Par conséquent, avant de simplement configurer mon projet pour ignorer la règle, je voudrais savoir si je manque quelque chose dans mon raisonnement.

Quand l'utilisation de len(SEQ)comme valeur de condition est-elle problématique? Quelles situations majeures Pylint tente-t-il d'éviter avec le C1801?

E_net4 de la brigade downvote
la source
9
Parce que vous pouvez évaluer directement la véracité de la séquence. pylint veut que vous fassiez if files:ouif not files:
Patrick Haugh
38
lenne connaît pas le contexte dans lequel il est appelé, donc si calculer la longueur signifie traverser la séquence entière, il le doit; il ne sait pas que le résultat est simplement comparé à 0. Le calcul de la valeur booléenne peut s'arrêter après avoir vu le premier élément, quelle que soit la durée réelle de la séquence. Je pense que pylint est un peu d'opinion ici, cependant; Je ne peux penser à aucune situation où il est mauvais d'utiliser len, juste que c'est une option pire que l'alternative.
chepner
2
@ E_net4 Je pense que PEP-8 est probablement le point de départ.
Patrick Haugh
6
Les séquences nécessitent un 'empty ()' ou 'isempty ()' comme imo C ++.
JDonner

Réponses:

281

Quand l'utilisation de len(SEQ)comme valeur de condition est-elle problématique? Quelles situations majeures Pylint tente-t-il d'éviter avec le C1801?

Ce n'est pas vraiment problématique à utiliser len(SEQUENCE)- bien qu'il ne soit pas aussi efficace (voir le commentaire de chepner ). Quoi qu'il en soit, Pylint vérifie la conformité du code avec le guide de style PEP 8 qui stipule que

Pour les séquences (chaînes, listes, tuples), utilisez le fait que les séquences vides sont fausses.

Yes: if not seq:
     if seq:

No:  if len(seq):
     if not len(seq):

En tant que programmeur Python occasionnel, qui passe d'une langue à l'autre, je considérerais la len(SEQUENCE)construction comme plus lisible et explicite («Explicit is better then implicit»). Cependant, l'utilisation du fait qu'une séquence vide est évaluée Falsedans un contexte booléen est considérée comme plus «Pythonique».

Anthony Geoghegan
la source
Comment faire ce travail alors:if len(fnmatch.filter(os.listdir(os.getcwd()), 'f_*')):
Marichyasana
@Marichyasana Je suppose que des choses comme ça peuvent (théoriquement) être écrites comme if next(iter(...), None) is not None:(si la séquence ne peut pas contenir None). C'est long, mais l' len(fnmatch...)est aussi; les deux doivent être séparés.
Kirill Bulygin
13
Je suis aussi un utilisateur occasionnel de Python et j'ai souvent l'impression que la "voie Pythonique" s'est en quelque sorte emmêlée dans sa propre ambiguïté.
luqo33
3
Juste une question générale, ces recommandations PEP peuvent-elles être révisées? Une autre raison pour laquelle le len(s) == 0est supérieur à mon avis est qu'il est généralisable pour d'autres types de séquences. Par exemple, pandas.Serieset les tableaux numpy. if not s:n'est pas d'autre part, et dans ce cas, vous devrez utiliser une évaluation distincte pour tous les types possibles d'objets de type tableaux (c.-à-d pd.DataFrame.empty.).
Marses
2
Soit dit en passant, aucune of collections.abcclasse n'indique la __bool__méthode. En d'autres termes, comment puis-je être sûr que je peux utiliser bool(seq)si je sais que c'est un collections.abc.Collection? De plus, certaines bibliothèques affirment qu'il est interdit de vérifier bool(collection)leurs classes.
Eir Nym
42

Notez que l'utilisation de len (seq) est en fait requise (au lieu de simplement vérifier la valeur booléenne de seq) lors de l'utilisation de tableaux NumPy.

a = numpy.array(range(10))
if a:
    print "a is not empty"

entraîne une exception: ValueError: la valeur de vérité d'un tableau avec plusieurs éléments est ambiguë. Utilisez a.any () ou a.all ()

Et donc pour le code qui utilise à la fois les listes Python et les tableaux NumPy, le message C1801 est loin d'être utile.

Cameron Hayne
la source
5
Je suis d'accord avec votre déclaration. Avec le problème # 1405 maintenant soulevé, j'espère voir C1801 reformé en quelque chose d'utile ou désactivé par défaut.
E_net4 de la brigade downvote
2
de plus, il est inutile de vérifier si une séquence a un nombre donné d'éléments. C'est seulement bon pour le vérifier, il est complètement vide dans le meilleur des cas.
PabTorre
1

Il s'agissait d'un problème dans Pylint, et il ne considère plus len(x) == 0 comme incorrect.

Vous ne devez pas utiliser un nu len(x) comme condition. Comparer len(x)avec une valeur explicite, comme celle if len(x) == 0deif len(x) > 0 est tout à fait correct et n'est pas interdit par PEP 8.

Depuis PEP 8 :

# Correct:
if not seq:
if seq:

# Wrong:
if len(seq):
if not len(seq):

Notez que le test explicite de la longueur n'est pas interdit. Le Zen de Python déclare:

Explicite vaut mieux qu'implicite.

Dans le choix entre if not seqet if not len(seq), les deux sont implicites mais le comportement est différent. Mais if len(seq) == 0ouif len(seq) > 0 sont des comparaisons explicites et dans de nombreux contextes le comportement correct.

Dans pylint, PR 2815 a corrigé ce bogue, signalé pour la première fois sous le numéro 2684 . Il continuera de se plaindre if len(seq), mais il ne se plaindra plus if len(seq) > 0. Le PR a été fusionné 2019-03-19, donc si vous utilisez pylint 2.4 (publié le 2019-09-14), vous ne devriez pas voir ce problème.

gerrit
la source
0

Pylint échouait pour mon code et la recherche m'a conduit à ce poste:

../filename.py:49:11: C1801: Do not use `len(SEQUENCE)` to determine if a sequence is empty (len-as-condition)
../filename.py:49:34: C1801: Do not use `len(SEQUENCE)` to determine if a sequence is empty (len-as-condition)

C'était mon code avant:

def list_empty_folders(directory):
"""The Module Has Been Build to list empty Mac Folders."""
for (fullpath, dirnames, filenames) in os.walk(directory):
    if len(dirnames) == 0 and len(filenames) == 0:
        print("Exists: {} : Absolute Path: {}".format(
            os.path.exists(fullpath), os.path.abspath(fullpath)))

C'était après ma correction de code. En utilisant le int() attribute, je semble avoir satisfait le Pep8 / Pylint et ne semble pas avoir un impact négatif sur mon code:

def list_empty_folders(directory):
"""The Module Has Been Build to list empty Mac Folders."""
for (fullpath, dirnames, filenames) in os.walk(directory):
    if len(dirnames).__trunc__() == 0 and len(filenames).__trunc__() == 0:
        print("Exists: {} : Absolute Path: {}".format(
            os.path.exists(fullpath), os.path.abspath(fullpath)))

My Fix

En ajoutant .__trunc__()à la séquence, il semble avoir réglé le besoin.

Je ne vois pas de différence dans le comportement, mais si quelqu'un connaît les détails qui me manquent, faites-le moi savoir.

JayRizzo
la source
1
Vous appelez __trunc__()la sortie de len(seq), qui tronque (quelque peu de manière redondante) la valeur de longueur en un entier. Il ne fait que «feindre» la charpie sans aborder la raison derrière elle. La suggestion de la réponse acceptée n'a-t-elle pas fonctionné pour vous?
E_net4 de la brigade de downvote le
Pas dans mes tentatives. Je comprends la redondance, mais même après que ce problème a été résolu par les développeurs dans github.com/PyCQA/pylint/issues/1405 & 2684 et a été fusionné, à ma connaissance, cela ne devrait pas être un problème lors de l'exécution de pylint mais Je vois toujours ce problème même après la mise à jour de mon pylône. Je voulais juste partager, car this worked for me, même si ce n'est pas tout à fait approprié. Mais, pour clarifier même si c'est redondant si vous faites une comparaison len (seq) == 0, trunc ne devrait rien avoir à faire car ils sont déjà des entiers. droite?
JayRizzo
1
Exactement, c'est déjà un entier et __trunc__()ne fait rien de significatif. Notez que je n'ai pas fait référence à la comparaison comme étant redondante, mais à cette tentative de tronquer la longueur. L'avertissement disparaît uniquement car il n'attend qu'une expression du formulaire len(seq) == 0. Je crois que la charpie dans ce cas s'attendrait à ce que vous if not dirnames and not filenames:
remplaciez la
Tester la véracité a les conséquences inattendues d'être "toujours vrai" si la __bool__fonction n'est pas définie dans la séquence sous-jacente.
Erik Aronesty