Почему `a == b или c или d` всегда принимает значение True?


110

Я пишу систему безопасности, которая запрещает доступ неавторизованным пользователям.

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

Он предоставляет доступ авторизованным пользователям, как и ожидалось, но также позволяет неавторизованным пользователям!

Hello. Please enter your name:
Bob
Access granted.

Почему это происходит? Я прямо заявил, что предоставляю доступ только nameКевину, Джону или Инбару. Я тоже пробовал обратную логику if "Kevin" or "Jon" or "Inbar" == name, но результат тот же.


1
@ Jean-François FYI было некоторое обсуждение этого вопроса и его обманутой цели ранее в комнате Python, обсуждение начинается здесь . Я понимаю, если вы хотите, чтобы оно было закрыто, но я подумал, что вы, возможно, захотите узнать о причинах, по которым сообщение было недавно повторно открыто. Полное раскрытие: Мартейн, автор ответа на цель обмана, еще не успел вмешаться в этот вопрос.
Andras Deak

Ответ Martijn просто превосходен, объясняя это словами «не используйте естественный язык», другие, ну ... это были славные времена голосования за ... Ответ ниже просто повторяет это. Для меня это дубликат. Но если Martijn решит снова открыться, я не против.
Жан-Франсуа Фабр

4
Вариации этой проблемы включают в себя x or y in z, x and y in z, x != y and zи некоторые другие. Хотя это не совсем то же самое, что и этот вопрос, основная причина у них одинакова. Просто хотел указать на это на случай, если кто-то закрыл свой вопрос как дубликат этого и не был уверен, насколько это актуально для них.
Аран-Фей

Ответы:


155

Во многих случаях Python выглядит и ведет себя как естественный английский, но это тот случай, когда эта абстракция терпит неудачу. Люди могут использовать контекстные подсказки, чтобы определить, что «Джон» и «Инбар» являются объектами, соединенными с глаголом «равно», но интерпретатор Python более буквальный.

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

логически эквивалентно:

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

Что для пользователя Боба эквивалентно:

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

orОператор выбирает первый аргумент с положительным значением истинности :

if ("Jon"):

И поскольку «Джон» имеет положительное значение истинности, ifблок выполняется. Это то, что вызывает печать «Доступ предоставлен» независимо от указанного имени.

Все эти рассуждения также применимы к выражению if "Kevin" or "Jon" or "Inbar" == name. первое значение, "Kevin"истинно, поэтому ifблок выполняется.


Есть два распространенных способа правильно построить это условие.

  1. Используйте несколько ==операторов для явной проверки каждого значения:
    if name == "Kevin" or name == "Jon" or name == "Inbar":

  2. Составьте последовательность допустимых значений и используйте inоператор для проверки принадлежности:
    if name in {"Kevin", "Jon", "Inbar"}:

В целом из двух следует предпочесть второе, так как его легче читать, а также быстрее:

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

Для тех, кому может понадобиться доказательство, if a == b or c or d or e: ...которое действительно разбирается таким образом. Встроенный astмодуль дает ответ:

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

Таким образом, testиз ifзаявления выглядит следующим образом :

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

Как можно видеть, это логический оператор orприменяется к нескольким values, а именно, a == bи c, dи e.


Есть ли особая причина выбирать кортеж ("Kevin", "Jon", "Inbar")вместо набора {"Kevin", "Jon", "Inbar"} ?
Human

2
Не совсем так, поскольку оба работают, если все значения хешируются. Тестирование членства в множестве имеет большую сложность, чем тестирование членства в кортеже, но создание набора немного дороже, чем создание кортежа. Я думаю, что это в основном отмывка для таких небольших коллекций. Игра с timeit a in {b, c, d}примерно в два раза быстрее, чем a in (b, c, d)на моей машине. Есть над чем подумать, если это критичный для производительности фрагмент кода.
Кевин

3
Кортеж или список при использовании in в предложении if? рекомендует установить литералы для тестирования членства. Я обновлю свой пост.
Кевин

В современном Python он распознает, что набор является константой, и frozensetвместо этого делает его , поэтому накладные расходы на построение набора отсутствуют. dis.dis(compile("1 in {1, 2, 3}", '<stdin>', 'eval'))
эндолит

1

Простая инженерная задача, давайте немного дальше.

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

Но унаследованный от языка C Python оценивает логическое значение ненулевого целого числа как True.

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

Теперь Python основывается на этой логике и позволяет использовать логические литералы, такие как или на целых числах, и т.

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

в заключение

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

Правильный способ написать это:

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

В целях безопасности я также рекомендую вам не кодировать жестко пароли.


1

Есть 3 проверки условий в if name == "Kevin" or "Jon" or "Inbar":

  • name == "Кевин"
  • "Джон"
  • «Инбар»

и этот оператор if эквивалентен

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

Поскольку elif "Jon"всегда будет истинным, доступ предоставляется любому пользователю

Решение


Вы можете использовать любой из приведенных ниже способов

Быстро

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

Медленный

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

Медленный + ненужный код

if name == "Kevin":
    print("Access granted.")
elif name == "Jon":
    print("Access granted.")
elif name == "Inbar":
    print("Access granted.")
else:
    print("Access denied.")
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.