Warum bewertet `a == b oder c oder d` immer True?


108

Ich schreibe ein Sicherheitssystem, das nicht autorisierten Benutzern den Zugriff verweigert.

import sys

print("Hello. Please enter your name:")
name = sys.stdin.readline().strip()
if name == "Kevin" or "Jon" or "Inbar":
    print("Access granted.")
else:
    print("Access denied.")

Es gewährt autorisierten Benutzern erwartungsgemäß Zugriff, lässt aber auch nicht autorisierte Benutzer zu!

Hello. Please enter your name:
Bob
Access granted.

Warum tritt das auf? Ich habe klar gesagt, dass ich nur dann Zugriff gewähren soll, wenn nameKevin, Jon oder Inbar gleichgestellt sind. Ich habe auch die entgegengesetzte Logik ausprobiert if "Kevin" or "Jon" or "Inbar" == name, aber das Ergebnis ist das gleiche.


1
@ Jean-François Zu Ihrer Information, es gab einige Diskussionen über diese Frage und ihr betrogenes Ziel früher im Python-Raum. Die Diskussion beginnt hier . Ich verstehe, wenn Sie es geschlossen haben möchten, aber ich dachte, Sie möchten vielleicht wissen, aus welchen Gründen der Beitrag kürzlich wieder geöffnet wurde. Vollständige Offenlegung: Martijn, der Autor der Antwort auf das betrügerische Ziel, hatte noch keine Zeit, sich auf die Angelegenheit einzulassen.
Andras Deak

Martijn Antwort ist einfach hervorragend, um es mit "benutze keine natürliche Sprache" zu erklären, andere, na ja, ... das waren glorreiche Abstimmungszeiten ... Die Antwort unten wiederholt dies nur. Für mich ist es ein Duplikat. Aber wenn Martijn sich für eine Wiedereröffnung entscheidet, macht es mir nichts aus.
Jean-François Fabre

4
Variationen dieses Problem sind x or y in z, x and y in z, x != y and zund ein paar andere. Obwohl dies nicht genau mit dieser Frage identisch ist, ist die Grundursache für alle gleich. Ich wollte nur darauf hinweisen, falls jemand seine Frage als Duplikat davon geschlossen hat und nicht sicher war, wie relevant sie für ihn ist.
Aran-Fey

Mögliches Duplikat von Wie
pppery

Antworten:


151

In vielen Fällen sieht Python wie natürliches Englisch aus und verhält sich auch so. In diesem Fall schlägt diese Abstraktion jedoch fehl. Menschen können Kontexthinweise verwenden, um festzustellen, dass "Jon" und "Inbar" Objekte sind, die mit dem Verb "gleich" verbunden sind, aber der Python-Interpreter ist wörtlicher.

if name == "Kevin" or "Jon" or "Inbar":

ist logisch äquivalent zu:

if (name == "Kevin") or ("Jon") or ("Inbar"):

Was für Benutzer Bob gleichbedeutend ist mit:

if (False) or ("Jon") or ("Inbar"):

Der orOperator wählt das erste Argument mit einem positiven Wahrheitswert :

if ("Jon"):

Und da "Jon" einen positiven Wahrheitswert hat, wird der ifBlock ausgeführt. Aus diesem Grund wird "Zugriff gewährt" unabhängig vom angegebenen Namen gedruckt.

All diese Überlegungen gelten auch für den Ausdruck if "Kevin" or "Jon" or "Inbar" == name. Der erste Wert "Kevin"ist true, daher wird der ifBlock ausgeführt.


Es gibt zwei übliche Möglichkeiten, diese Bedingung richtig zu konstruieren.

  1. Verwenden Sie mehrere ==Operatoren, um explizit mit jedem Wert zu vergleichen:
    if name == "Kevin" or name == "Jon" or name == "Inbar":

  2. Erstellen Sie eine Folge gültiger Werte und intesten Sie mit dem Operator die Mitgliedschaft:
    if name in {"Kevin", "Jon", "Inbar"}:

Im Allgemeinen sollte die zweite bevorzugt werden, da sie leichter zu lesen und auch schneller ist:

>>> import timeit
>>> timeit.timeit('name == "Kevin" or name == "Jon" or name == "Inbar"', setup="name='Inbar'")
0.4247764749999945
>>> timeit.timeit('name in {"Kevin", "Jon", "Inbar"}', setup="name='Inbar'")
0.18493307199999265

Für diejenigen, die Beweise wollen, if a == b or c or d or e: ...die tatsächlich so analysiert werden. Das eingebaute astModul bietet eine Antwort:

>>> import ast
>>> ast.parse("if a == b or c or d or e: ...")
<_ast.Module object at 0x1031ae6a0>
>>> ast.dump(_)
"Module(body=[If(test=BoolOp(op=Or(), values=[Compare(left=Name(id='a', ctx=Load()), ops=[Eq()], comparators=[Name(id='b', ctx=Load())]), Name(id='c', ctx=Load()), Name(id='d', ctx=Load()), Name(id='e', ctx=Load())]), body=[Expr(value=Ellipsis())], orelse=[])])"
>>>

So ist die testder ifAussage sieht wie folgt aus :

BoolOp(
 op=Or(),
 values=[
  Compare(
   left=Name(id='a', ctx=Load()),
   ops=[Eq()],
   comparators=[Name(id='b', ctx=Load())]
  ),
  Name(id='c', ctx=Load()),
  Name(id='d', ctx=Load()),
  Name(id='e', ctx=Load())
 ]
)

Wie man sehen kann, ist es der Booleschen Operator orauf mehr angewandt values, nämlich a == bund c, dund e.


Gibt es einen bestimmten Grund, ein Tupel ("Kevin", "Jon", "Inbar")anstelle eines Satzes zu wählen {"Kevin", "Jon", "Inbar"} ?
Mensch

2
Nicht wirklich, da beide funktionieren, wenn alle Werte hashbar sind. Das Testen der Set-Mitgliedschaft hat eine bessere Big-O-Komplexität als das Testen der Tupel-Mitgliedschaft, aber das Erstellen eines Sets ist etwas teurer als das Erstellen eines Tupels. Ich denke, es ist größtenteils eine Wäsche für kleine Sammlungen wie diese. Mit Timeit herumzuspielen a in {b, c, d}ist ungefähr doppelt so schnell wie a in (b, c, d)auf meinem Computer. Denken Sie darüber nach, ob dies ein leistungskritischer Code ist.
Kevin

3
Tupel oder Liste bei Verwendung von 'in' in einer 'if'-Klausel? empfiehlt, Literale für Mitgliedschaftstests festzulegen. Ich werde meinen Beitrag aktualisieren.
Kevin

In modernem Python erkennt es, dass die Menge eine Konstante ist, und macht sie frozensetstattdessen zu einer Konstante , sodass der Overhead der Konstruktionsmenge nicht vorhanden ist. dis.dis(compile("1 in {1, 2, 3}", '<stdin>', 'eval'))
Endolith

1

Einfaches Engineering-Problem, lassen Sie es uns einfach etwas weiter gehen.

In [1]: a,b,c,d=1,2,3,4
In [2]: a==b
Out[2]: False

Python, das von der Sprache C geerbt wurde, wertet den logischen Wert einer Ganzzahl ungleich Null als True aus.

In [11]: if 3:
    ...:     print ("yey")
    ...:
yey

Jetzt baut Python auf dieser Logik auf und lässt Sie Logikliterale wie oder auf Ganzzahlen usw. verwenden

In [9]: False or 3
Out[9]: 3

Schließlich

In [4]: a==b or c or d
Out[4]: 3

Die richtige Art, es zu schreiben, wäre:

In [13]: if a in (b,c,d):
    ...:     print('Access granted')

Aus Sicherheitsgründen würde ich auch vorschlagen, dass Sie keine festen Kennwörter verwenden.


1

Es gibt 3 Zustandsüberprüfungen if name == "Kevin" or "Jon" or "Inbar":

  • name == "Kevin"
  • "Jon"
  • "Inbar"

und diese if-Anweisung ist äquivalent zu

if name == "Kevin":
    print("Access granted.")
elif "Jon":
    print("Access granted.")
elif "Inbar":
    print("Access granted.")
else:
    print("Access denied.")

Da elif "Jon"immer wahr sein wird, wird der Zugriff auf jeden Benutzer gewährt

Lösung


Sie können eine der folgenden Methoden verwenden

Schnell

if name in ["Kevin", "Jon", "Inbar"]:
    print("Access granted.")
else:
    print("Access denied.")

Langsam

if name == "Kevin" or name == "Jon" or name == "Inbar":
    print("Access granted.")
else:
    print("Access denied.")

Langsam + Unnötiger Code

if name == "Kevin":
    print("Access granted.")
elif name == "Jon":
    print("Access granted.")
elif name == "Inbar":
    print("Access granted.")
else:
    print("Access denied.")
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.