Pourquoi `a == b ou c ou d` est-il toujours évalué à True?

109

J'écris un système de sécurité qui refuse l'accès aux utilisateurs non autorisés.

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

Il accorde l'accès aux utilisateurs autorisés comme prévu, mais il permet également aux utilisateurs non autorisés!

Hello. Please enter your name:
Bob
Access granted.

Pourquoi cela se produit-il? J'ai clairement déclaré de n'accorder l'accès que lorsque c'est nameégal à Kevin, Jon ou Inbar. J'ai aussi essayé la logique inverse if "Kevin" or "Jon" or "Inbar" == name, mais le résultat est le même.

Kevin
la source
1
@ Jean-François Pour info il y a eu une discussion sur cette question et sa cible de dupe plus tôt dans la salle python, la discussion commence ici . Je comprends que vous souhaitiez le fermer, mais j'ai pensé que vous souhaitiez peut-être connaître les raisons pour lesquelles le message a été récemment rouvert. Divulgation complète: Martijn, l'auteur de la réponse sur la cible dupe n'a pas encore eu le temps de se prononcer sur la question.
Andras Deak
La réponse de Martijn est juste excellente en l'expliquant avec "n'utilisez pas le langage naturel", d'autres, eh bien, ... c'était des temps de vote ascendants glorieux ... La réponse ci-dessous ne fait que répéter cela. Pour moi, c'est un doublon. Mais si Martijn choisit de rouvrir, eh bien, cela ne me dérange pas.
Jean-François Fabre
4
Les variations de ce problème comprennent x or y in z, x and y in z, x != y and zet quelques autres. Bien qu'elle ne soit pas exactement identique à cette question, la cause première est la même pour tous. Je voulais juste le signaler au cas où quelqu'un aurait fermé sa question comme un double et ne serait pas sûr de sa pertinence pour lui.
Aran-Fey

Réponses:

155

Dans de nombreux cas, Python ressemble et se comporte comme un anglais naturel, mais c'est un cas où cette abstraction échoue. Les gens peuvent utiliser des indices de contexte pour déterminer que «Jon» et «Inbar» sont des objets joints au verbe «égal», mais l'interpréteur Python est plus littéral.

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

équivaut logiquement à:

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

Ce qui, pour l'utilisateur Bob, équivaut à:

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

L' oropérateur choisit le premier argument avec une valeur de vérité positive :

if ("Jon"):

Et comme "Jon" a une valeur de vérité positive, le ifbloc s'exécute. C'est ce qui provoque l'impression de "Accès accordé" quel que soit le nom donné.

Tout ce raisonnement s'applique également à l'expression if "Kevin" or "Jon" or "Inbar" == name . la première valeur "Kevin",, est vraie, donc le ifbloc s'exécute.


Il existe deux façons courantes de construire correctement ce conditionnel.

  1. Utilisez plusieurs ==opérateurs pour vérifier explicitement chaque valeur:
    if name == "Kevin" or name == "Jon" or name == "Inbar":

  2. Composez une séquence de valeurs valides et utilisez l' inopérateur pour tester l'appartenance:
    if name in {"Kevin", "Jon", "Inbar"}:

En général, le second devrait être préféré car il est plus facile à lire et aussi plus rapide:

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

Pour ceux qui veulent une preuve qui if a == b or c or d or e: ...est en effet analysée comme ça. Le astmodule intégré fournit une réponse:

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

Ainsi , le testdes ifétats se présente comme suit:

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

Comme on peut le voir, il est l'opérateur booléen orappliqué à plusieurs values, à savoir, a == bet c, det e.

Kevin
la source
Y a-t-il une raison spécifique de choisir un tuple ("Kevin", "Jon", "Inbar")au lieu d'un ensemble {"Kevin", "Jon", "Inbar"} ?
Human
2
Pas vraiment, car les deux fonctionnent si les valeurs sont toutes hachables. Le test d'appartenance à l'ensemble a une meilleure complexité big-O que le test d'appartenance à un tuple, mais la construction d'un ensemble est un peu plus coûteuse que la construction d'un tuple. Je pense que c'est en grande partie un lavage pour les petites collections comme celles-ci. Jouer avec timeit a in {b, c, d}est environ deux fois plus rapide que a in (b, c, d)sur ma machine. Quelque chose à penser s'il s'agit d'un morceau de code critique pour les performances.
Kevin le
3
Tuple ou liste lors de l'utilisation de 'in' dans une clause 'if'? recommande de définir des littéraux pour les tests d'appartenance. Je mettrai à jour mon message.
Kevin le
En Python moderne, il reconnaît que l'ensemble est une constante et en fait un à la frozensetplace, de sorte que la surcharge de l'ensemble de construction n'est pas là. dis.dis(compile("1 in {1, 2, 3}", '<stdin>', 'eval'))
endolithe
1

Problème d'ingénierie simple, allons simplement un peu plus loin.

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

Mais, hérité du langage C, Python évalue la valeur logique d'un entier non nul comme True.

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

Maintenant, Python s'appuie sur cette logique et vous permet d'utiliser des littéraux logiques tels que ou sur des entiers, etc.

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

finalement

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

La bonne façon de l'écrire serait:

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

Pour plus de sécurité, je vous suggère également de ne pas coder en dur les mots de passe.

user1854182
la source
1

Il y a 3 contrôles de condition if name == "Kevin" or "Jon" or "Inbar":

  • nom == "Kevin"
  • «Jon»
  • "Inbar"

et cette instruction if équivaut à

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

Puisque elif "Jon" sera toujours vrai, l'accès à n'importe quel utilisateur est accordé

Solution


Vous pouvez utiliser l'une des méthodes ci-dessous

Vite

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

Lent

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

Code lent + inutile

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