Warum wird die Verwendung von len (SEQUENCE) in Bedingungswerten von Pylint als falsch angesehen?

211

In Anbetracht dieses Code-Snippets:

from os import walk

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

Ich wurde von Pylint mit dieser Meldung bezüglich der Zeile mit der if-Anweisung alarmiert:

[pylint] C1801: Nicht verwenden len(SEQUENCE) als Bedingungswert verwenden

Die Regel C1801 klang auf den ersten Blick für mich nicht sehr vernünftig, und die Definition im Referenzhandbuch erklärt nicht, warum dies ein Problem ist. Tatsächlich nennt es es geradezu eine falsche Verwendung .

len-as-condition (C1801) : Nicht len(SEQUENCE)als Bedingungswert verwenden Wird verwendet, wenn Pylint eine falsche Verwendung von len (Sequenz) innerhalb von Bedingungen feststellt.

Meine Suchversuche haben mir auch keine tiefere Erklärung geliefert. Ich verstehe, dass die Längeneigenschaft einer Sequenz möglicherweise träge ausgewertet wird und __len__so programmiert werden kann, dass sie Nebenwirkungen hat, aber es ist fraglich, ob dies allein für Pylint problematisch genug ist, um eine solche Verwendung als falsch zu bezeichnen. Bevor ich mein Projekt einfach so konfiguriere, dass die Regel ignoriert wird, möchte ich wissen, ob mir etwas in meiner Argumentation fehlt.

Wann ist die Verwendung len(SEQ)als Bedingungswert problematisch? Welche wichtigen Situationen versucht Pylint mit C1801 zu vermeiden?

E_net4 ist eine Ablehnung
quelle
9
Weil Sie die Wahrhaftigkeit der Sequenz direkt bewerten können. Pylint will, dass Sie tun if files:oderif not files:
Patrick Haugh
38
lenkennt den Kontext, in dem es aufgerufen wird, nicht. Wenn das Berechnen der Länge das Durchlaufen der gesamten Sequenz bedeutet, muss es; Es ist nicht bekannt, dass das Ergebnis nur mit 0 verglichen wird. Die Berechnung des Booleschen Werts kann beendet werden, nachdem das erste Element angezeigt wurde, unabhängig davon, wie lang die Sequenz tatsächlich ist. Ich denke, Pylint ist hier allerdings ein bisschen eigensinnig; Ich kann mir keine Situation vorstellen, in der die Verwendung falsch istlen , nur dass es eine schlechtere Option als die Alternative ist.
Chepper
2
@ E_net4 Ich denke, dass PEP-8 wahrscheinlich der Ausgangspunkt ist.
Patrick Haugh
6
SEQUENCES benötigen ein 'empty ()' oder 'isempty ()' wie C ++ imo.
JDonner

Antworten:

281

Wann ist die Verwendung len(SEQ)als Bedingungswert problematisch? Welche wichtigen Situationen versucht Pylint mit C1801 zu vermeiden?

Die Verwendung ist nicht wirklich problematisch len(SEQUENCE)- obwohl sie möglicherweise nicht so effizient ist (siehe chepners Kommentar ). Unabhängig davon überprüft Pylint den Code auf Übereinstimmung mit dem PEP 8- Styleguide , in dem dies angegeben ist

Verwenden Sie für Sequenzen (Zeichenfolgen, Listen, Tupel) die Tatsache, dass leere Sequenzen falsch sind.

Yes: if not seq:
     if seq:

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

Als gelegentlicher Python-Programmierer, der zwischen Sprachen wechselt, würde ich das len(SEQUENCE)Konstrukt als lesbarer und expliziter betrachten („Explizit ist besser als implizit“). Die Verwendung der Tatsache, dass eine leere Sequenz Falsein einem booleschen Kontext ausgewertet wird, wird jedoch als „pythonischer“ angesehen.

Anthony Geoghegan
quelle
Wie man diese Arbeit dann macht:if len(fnmatch.filter(os.listdir(os.getcwd()), 'f_*')):
Marichyasana
@ Marichyasana Ich denke, solche Dinge können (theoretisch) so geschrieben werden if next(iter(...), None) is not None:(wenn die Sequenz nicht enthalten kann None). Das ist lang, aber das len(fnmatch...)ist auch lang; beide müssen aufgeteilt werden.
Kirill Bulygin
13
Ich bin auch ein gelegentlicher Python-Benutzer und habe oft den Eindruck, dass sich der "pythonische Weg" in seiner eigenen Mehrdeutigkeit verwickelt hat.
Luqo33
3
Nur eine allgemeine Frage: Können diese PEP-Empfehlungen überarbeitet werden? Ein weiterer Grund, warum das len(s) == 0meiner Meinung nach überlegen ist, ist, dass es für andere Arten von Sequenzen verallgemeinerbar ist. Zum Beispiel pandas.Seriesund numpy Arrays. if not s:ist nicht auf der anderen Seite, und in diesem Fall müssten Sie eine separate Auswertung für alle möglichen Arten von Arrays-ähnlichen Objekten (dh pd.DataFrame.empty) verwenden.
Marses
2
Übrigens gibt keine of collections.abcKlasse die __bool__Methode an. Mit anderen Worten, wie kann ich sicher sein, dass ich es verwenden kann, bool(seq)wenn ich weiß, dass es ein ist collections.abc.Collection? Moreso, einige Bibliotheken erklären, dass es verboten ist, bool(collection)nach ihren Klassen zu suchen .
Eir Nym
42

Beachten Sie, dass bei Verwendung von NumPy-Arrays tatsächlich die Verwendung von len (seq) erforderlich ist (anstatt nur den Bool-Wert von seq zu überprüfen).

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

führt zu einer Ausnahme: ValueError: Der Wahrheitswert eines Arrays mit mehr als einem Element ist nicht eindeutig. Verwenden Sie a.any () oder a.all ()

Für Code, der sowohl Python-Listen als auch NumPy-Arrays verwendet, ist die Nachricht C1801 daher weniger hilfreich.

Cameron Hayne
quelle
5
Ich stimme Ihrer Aussage zu. Mit 1405 issue # jetzt angehoben, ich hoffe , standardmäßig zu sehen C1801 entweder reformiert etwas Nützliches oder deaktiviert.
E_net4 ist eine Abwertung
2
Außerdem ist es nutzlos zu überprüfen, ob eine Sequenz eine bestimmte Anzahl von Elementen enthält. Es ist nur gut, um zu überprüfen, ob es im besten Fall vollständig leer ist.
PabTorre
1

Dies war ein Problem bei Pylint und wird nicht mehr len(x) == 0als falsch angesehen.

Sie sollten kein Bare len(x) als Bedingung verwenden. Vergleichenlen(x) mit einem expliziten Wert wie if len(x) == 0von if len(x) > 0ist völlig in Ordnung und wird von PEP 8 nicht verboten.

Aus PEP 8 :

# Correct:
if not seq:
if seq:

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

Beachten Sie, dass das explizite Testen der Länge nicht verboten ist. Das Zen von Python besagt:

Explizit ist besser als implizit.

Bei der Wahl zwischen if not seqund if not len(seq)sind beide implizit, aber das Verhalten ist unterschiedlich. Aber if len(seq) == 0oderif len(seq) > 0 sind explizite Vergleiche und in vielen Zusammenhängen das richtige Verhalten.

In Pylint hat PR 2815 diesen Fehler behoben, der zuerst als Problem 2684 gemeldet wurde . Es wird sich weiterhin beschweren if len(seq), aber es wird sich nicht mehr beschweren if len(seq) > 0. Die PR wurde am 19.03.2019 zusammengeführt. Wenn Sie also Pylint 2.4 (veröffentlicht am 14.09.2019) verwenden, sollte dieses Problem nicht auftreten.

gerrit
quelle
0

Pylint versagte bei meinem Code und die Recherche führte mich zu diesem Beitrag:

../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)

Dies war mein Code vor:

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)))

Dies war nach meinem Code-Fix. Durch die Verwendung von int() attributescheint ich den Pep8 / Pylint zufrieden zu stellen und habe keinen negativen Einfluss auf meinen 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)))

Mein Fix

Durch das Hinzufügen .__trunc__()zur Sequenz scheint der Bedarf gedeckt zu sein.

Ich sehe keinen Unterschied im Verhalten, aber wenn jemand Einzelheiten weiß, die mir fehlen, lassen Sie es mich bitte wissen.

JayRizzo
quelle
1
Sie rufen __trunc__()die Ausgabe von auf len(seq), die (etwas redundant) den Längenwert auf eine Ganzzahl abschneidet. Es "täuscht" nur die Flusen, ohne den Grund dafür anzusprechen. Hat der Vorschlag in der akzeptierten Antwort nicht für Sie funktioniert?
E_net4 ist eine Abwertung
Nicht bei meinen Versuchen. Ich verstehe die Redundanz, aber selbst nachdem dieses Problem von den Entwicklern in github.com/PyCQA/pylint/issues/1405 & 2684 behoben und zusammengeführt wurde, sollte dies meines Erachtens kein Problem sein, wenn Pylint ausgeführt wird Ich sehe dieses Problem auch nach der Aktualisierung meines Pylint. Ich wollte nur teilen, this worked for meauch wenn es nicht ganz angemessen ist. Um zu verdeutlichen, auch wenn es redundant ist, wenn Sie einen len (seq) == 0-Vergleich durchführen, sollte trunc nichts tun müssen, da es sich bereits um Ganzzahlen handelt. richtig?
JayRizzo
1
Genau, es ist bereits eine ganze Zahl und __trunc__()macht nichts Sinnvolles. Beachten Sie, dass ich den Vergleich nicht als redundant bezeichnet habe, sondern auf diesen Versuch, die Länge zu kürzen. Die Warnung verschwindet nur, weil sie nur einen Ausdruck des Formulars erwartet len(seq) == 0. Ich glaube, dass die Flusen in diesem Fall erwarten würden, dass Sie die if-Anweisung durch die folgende ersetzen:if not dirnames and not filenames:
E_net4 ist eine Abwertung
Das Testen auf Wahrhaftigkeit hat die unbeabsichtigten Konsequenzen, "immer wahr" zu sein, wenn die __bool__Funktion nicht in der zugrunde liegenden Sequenz definiert ist.
Erik Aronesty