Dlaczego argument `a == b lub c lub d` zawsze przyjmuje wartość True?

110

Piszę system bezpieczeństwa, który odmawia dostępu nieautoryzowanym użytkownikom.

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

Zapewnia dostęp upoważnionym użytkownikom zgodnie z oczekiwaniami, ale także umożliwia dostęp nieautoryzowanym użytkownikom!

Hello. Please enter your name:
Bob
Access granted.

Dlaczego tak się dzieje? Wyraźnie stwierdziłem, że udzielam dostępu tylko wtedy, gdy namejest równy Kevinowi, Jonowi lub Inbarowi. Próbowałem też odwrotnej logiki if "Kevin" or "Jon" or "Inbar" == name, ale wynik jest taki sam.

Kevin
źródło
1
@ Jean-François FYI odbyła się dyskusja na temat tego pytania i jego fałszywego celu wcześniej w pokoju Pythona, dyskusja zaczyna się tutaj . Rozumiem, jeśli chcesz go zamknąć, ale pomyślałem, że możesz chcieć poznać powody, dla których post został niedawno ponownie otwarty. Pełne ujawnienie: Martijn, autor odpowiedzi na fałszywy cel, nie miał jeszcze czasu, aby wtrącić się w tej sprawie.
Andras Deak
Odpowiedź Martijna jest po prostu znakomita, wyjaśniając to za pomocą „nie używaj języka naturalnego”, inne, cóż, ... to były wspaniałe czasy głosowania za ... Poniższa odpowiedź tylko to powtarza. Dla mnie to duplikat. Ale jeśli Martijn zdecyduje się ponownie otworzyć, cóż, nie mam nic przeciwko.
Jean-François Fabre
4
Odmiany tego problemu obejmują x or y in z, x and y in z, x != y and zi kilka innych. Chociaż nie jest to dokładnie identyczne z tym pytaniem, główna przyczyna jest taka sama dla wszystkich. Chciałem tylko zwrócić uwagę na to, na wypadek gdyby ktoś zamknął swoje pytanie jako duplikat tego i nie był pewien, jakie ma dla niego znaczenie.
Aran-Fey

Odpowiedzi:

155

W wielu przypadkach Python wygląda i zachowuje się jak naturalny angielski, ale jest to jeden przypadek, w którym ta abstrakcja zawodzi. Ludzie mogą używać wskazówek kontekstowych, aby określić, że „Jon” i „Inbar” są obiektami połączonymi z czasownikiem „równa się”, ale interpreter Pythona jest bardziej dosłowny.

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

jest logicznie równoważne z:

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

Co dla użytkownika Bob jest równoważne z:

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

orOperator wybiera pierwszy argument z pozytywnej wartości logicznej :

if ("Jon"):

A ponieważ „Jon” ma dodatnią wartość prawdy, ifblok jest wykonywany. To właśnie powoduje, że "Przyznany dostęp" jest drukowany niezależnie od podanej nazwy.

Całe to rozumowanie odnosi się również do wyrażenia if "Kevin" or "Jon" or "Inbar" == name. pierwsza wartość "Kevin"to prawda, więc ifblok jest wykonywany.


Istnieją dwa typowe sposoby prawidłowego skonstruowania tego warunku.

  1. Użyj wielu ==operatorów, aby jawnie sprawdzić każdą wartość:
    if name == "Kevin" or name == "Jon" or name == "Inbar":

  2. Utwórz sekwencję prawidłowych wartości i użyj inoperatora, aby przetestować członkostwo:
    if name in {"Kevin", "Jon", "Inbar"}:

Ogólnie rzecz biorąc, druga powinna być preferowana, ponieważ jest łatwiejsza do odczytania, a także szybsza:

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

Dla tych, którzy mogą chcieć dowodu, który if a == b or c or d or e: ...rzeczywiście jest analizowany w ten sposób. Wbudowany astmoduł dostarcza odpowiedzi:

>>> 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=[])])"
>>>

Więc testz ifwyglądem rachunku, takich jak ten:

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

Jak widać, jest to operator logiczna orzastosowana do wielokrotnego values, a mianowicie, a == bi c, di e.

Kevin
źródło
Czy istnieje konkretny powód, dla którego warto wybrać krotkę ("Kevin", "Jon", "Inbar")zamiast zestawu {"Kevin", "Jon", "Inbar"} ?
Człowiek
2
Niezupełnie, ponieważ oba działają, jeśli wszystkie wartości są haszowane. Testowanie członkostwa zestawu ma lepszą złożoność big-O niż testowanie członkostwa krotki, ale skonstruowanie zestawu jest trochę droższe niż skonstruowanie krotki. Myślę, że to w dużej mierze pranie dla małych kolekcji, takich jak te. Zabawa z czasem a in {b, c, d}jest około dwa razy szybsza niż a in (b, c, d)na moim komputerze. Coś do przemyślenia, jeśli jest to fragment kodu krytyczny dla wydajności.
Kevin
3
Krotka lub lista, gdy używasz „in” w klauzuli „if”? zaleca zestaw literałów do testowania członkostwa. Zaktualizuję swój post.
Kevin,
We współczesnym Pythonie rozpoznaje, że zbiór jest stałą i czyni go frozensetzamiast tego, więc narzut zestawu konstrukcyjnego nie istnieje. dis.dis(compile("1 in {1, 2, 3}", '<stdin>', 'eval'))
endolit
1

Prosty problem inżynieryjny, przejdźmy dalej.

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

Ale, odziedziczony z języka C, Python ocenia wartość logiczną niezerowej liczby całkowitej jako True.

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

Teraz Python opiera się na tej logice i pozwala używać literałów logicznych, takich jak lub na liczbach całkowitych, i tak

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

Wreszcie

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

Prawidłowy sposób zapisu to:

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

Ze względów bezpieczeństwa sugerowałbym również, aby nie kodować haseł na stałe.

user1854182
źródło
1

Istnieją 3 warunki sprawdzania if name == "Kevin" or "Jon" or "Inbar":

  • name == "Kevin"
  • „Jon”
  • "W barze"

i to jeśli instrukcja jest równoważna

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

Ponieważ elif "Jon"zawsze będzie prawdziwe, więc dostęp do każdego użytkownika jest przyznany

Rozwiązanie


Możesz użyć jednej z poniższych metod

Szybki

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

Powolny

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

Powolny + niepotrzebny kod

if name == "Kevin":
    print("Access granted.")
elif name == "Jon":
    print("Access granted.")
elif name == "Inbar":
    print("Access granted.")
else:
    print("Access denied.")
7u5h4r
źródło