Perché "a == b o c o d" restituisce sempre True?


108

Sto scrivendo un sistema di sicurezza che nega l'accesso a utenti non autorizzati.

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

Concede l'accesso agli utenti autorizzati come previsto, ma consente anche l'accesso agli utenti non autorizzati!

Hello. Please enter your name:
Bob
Access granted.

Perché accade questo? Ho chiaramente affermato di concedere l'accesso solo quando è nameuguale a Kevin, Jon o Inbar. Ho provato anche la logica opposta if "Kevin" or "Jon" or "Inbar" == name, ma il risultato è lo stesso.


1
@ Jean-François Cordiali saluti, ci sono state alcune discussioni su questa domanda e sul suo obiettivo ingannevole nella stanza dei pitoni, la discussione inizia qui . Capisco se vuoi che venga chiuso, ma ho pensato che potresti voler sapere i motivi per cui il post è stato riaperto di recente. Divulgazione completa: Martijn, l'autore della risposta sul bersaglio ingannevole, non ha ancora avuto il tempo di intervenire sulla questione.
Andras Deak

La risposta di Martijn è semplicemente eccellente spiegandola con "non usare il linguaggio naturale", altri, beh, ... quelli erano tempi gloriosi di upvoting ... La risposta sotto lo ripete. Per me è un duplicato. Ma se Martijn decide di riaprire, beh, non mi dispiace.
Jean-François Fabre

4
Variazioni di questo problema includono x or y in z, x and y in z, x != y and ze pochi altri. Sebbene non esattamente identica a questa domanda, la causa principale è la stessa per tutti loro. Volevo solo farlo notare nel caso qualcuno avesse chiuso la domanda come duplicato di questo e non fosse sicuro di quanto fosse rilevante per loro.
Aran-Fey

Risposte:


152

In molti casi, Python sembra e si comporta come l'inglese naturale, ma questo è un caso in cui tale astrazione fallisce. Le persone possono usare indizi di contesto per determinare che "Jon" e "Inbar" sono oggetti uniti al verbo "uguale", ma l'interprete Python ha una mentalità più letterale.

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

è logicamente equivalente a:

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

Che, per l'utente Bob, equivale a:

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

L' oroperatore sceglie il primo argomento con un valore di verità positivo :

if ("Jon"):

E poiché "Jon" ha un valore di verità positivo, il file if blocco viene eseguito. Questo è ciò che causa la stampa di "Accesso concesso" indipendentemente dal nome assegnato.

Tutto questo ragionamento vale anche per l'espressione if "Kevin" or "Jon" or "Inbar" == name. il primo valore "Kevin",, è vero, quindi il ifblocco viene eseguito.


Esistono due modi comuni per costruire correttamente questo condizionale.

  1. Utilizza più ==operatori per verificare esplicitamente ogni valore:
    if name == "Kevin" or name == "Jon" or name == "Inbar":

  2. Componi una sequenza di valori validi e utilizza l' inoperatore per verificare l'appartenenza:
    if name in {"Kevin", "Jon", "Inbar"}:

In generale dei due va preferito il secondo in quanto è più facile da leggere e anche più veloce:

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

Per coloro che desiderano una prova che if a == b or c or d or e: ...è effettivamente analizzata in questo modo. Il astmodulo integrato fornisce una risposta:

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

Quindi il testdella ifdichiarazione assomiglia a questo:

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

Come si può vedere, è l'operatore booleano orapplicato a molteplici values, cioè, a == be c, de e.


C'è un motivo specifico per scegliere una tupla ("Kevin", "Jon", "Inbar")invece di un insieme {"Kevin", "Jon", "Inbar"} ?
Umano

2
Non proprio, poiché entrambi funzionano se i valori sono tutti modificabili. Il test di appartenenza a un insieme ha una complessità maggiore rispetto al test di appartenenza alla tupla, ma la costruzione di un insieme è un po 'più costosa della costruzione di una tupla. Penso che sia in gran parte un lavaggio per piccole collezioni come queste. Giocare con il tempo a in {b, c, d}è circa il doppio più veloce a in (b, c, d)della mia macchina. Qualcosa a cui pensare se questo è un pezzo di codice critico per le prestazioni.
Kevin

3
Tupla o elenco quando si utilizza "in" in una clausola "if"? consiglia di impostare valori letterali per il test dell'appartenenza. Aggiornerò il mio post.
Kevin

Nel moderno Python, riconosce che l'insieme è una costante e lo rende frozensetinvece un , quindi l'overhead di costruzione dell'insieme non è presente. dis.dis(compile("1 in {1, 2, 3}", '<stdin>', 'eval'))
endolith

1

Semplice problema di ingegneria, andiamo semplicemente un po 'oltre.

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

Ma, ereditato dal linguaggio C, Python valuta il valore logico di un numero intero diverso da zero come True.

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

Ora, Python si basa su quella logica e ti consente di usare letterali logici come o su numeri interi, e così via

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

Finalmente

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

Il modo corretto per scriverlo sarebbe:

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

Per sicurezza suggerirei anche di non codificare le password.


1

Sono disponibili 3 controlli delle condizioni if name == "Kevin" or "Jon" or "Inbar":

  • nome == "Kevin"
  • "Jon"
  • "Inbar"

e questa istruzione if è equivalente a

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

Da elif "Jon" sarà sempre vero quindi l'accesso a qualsiasi utente è concesso

Soluzione


Puoi utilizzare uno qualsiasi dei metodi seguenti

Veloce

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

Lento

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

Codice lento + non necessario

if name == "Kevin":
    print("Access granted.")
elif name == "Jon":
    print("Access granted.")
elif name == "Inbar":
    print("Access granted.")
else:
    print("Access denied.")
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.