Чому `a == b або c або d` завжди оцінюється як True?


108

Я пишу систему безпеки, яка забороняє доступ стороннім користувачам.

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 раніше було обговорено це питання та його цільову мету раніше в кімнаті пітона, тут розпочинається дискусія . Я розумію, якщо ви хочете, щоб його закрили, але я подумав, що ви можете дізнатися про причини, через які посаду нещодавно відкрили. Повне оприлюднення: Мартійн, автор відповіді на ціль дупа, ще не встиг прозвучити цю справу.
Андрас

Відповідь Мартійна просто чудова, пояснюючи це "не використовуйте природну мову", інші ж, добре, що ... це були славні часи позову ... Відповідь нижче просто це повторює. Для мене це дублікат. Але якщо Мартійн вирішить знову відкрити, я не проти.
Жан-Франсуа Фабре

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

Відповіді:


153

У багатьох випадках 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"} ?
Людина

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

3
Змінити чи списувати під час використання "в" у пункті "якщо"? рекомендує встановити літерали для тестування членства. Я оновлю свою посаду.
Кевін

У сучасному 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 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.