เหตุใด "a == b หรือ c หรือ d` จึงประเมินเป็น True เสมอ


109

ฉันกำลังเขียนระบบความปลอดภัยที่ปฏิเสธการเข้าถึงของผู้ใช้ที่ไม่ได้รับอนุญาต

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เท่ากับ Kevin, Jon หรือ Inbar เท่านั้น ฉันได้ลองใช้ตรรกะตรงข้ามif "Kevin" or "Jon" or "Inbar" == nameแล้ว แต่ผลลัพธ์ก็เหมือนกัน


1
@ Jean-François FYI มีการอภิปรายเกี่ยวกับคำถามนี้และเป้าหมายล่อก่อนหน้านี้ในห้องพักหลามอภิปรายเริ่มต้นที่นี่ ฉันเข้าใจว่าคุณต้องการปิดหรือไม่ แต่ฉันคิดว่าคุณอาจต้องการทราบเกี่ยวกับสาเหตุที่เพิ่งเปิดโพสต์ใหม่ การเปิดเผยข้อมูลทั้งหมด: Martijn ผู้เขียนคำตอบเกี่ยวกับเป้าหมายที่หลอกลวงยังไม่มีเวลาตีระฆังในเรื่องนี้
Andras Deak

คำตอบของ Martijn เป็นเพียงการอธิบายที่ยอดเยี่ยมด้วย "อย่าใช้ภาษาที่เป็นธรรมชาติ" คนอื่น ๆ ... นั่นเป็นช่วงเวลาที่มีการโหวตที่น่ายินดี ... คำตอบด้านล่างนี้ซ้ำแล้วซ้ำอีก สำหรับฉันมันซ้ำกัน แต่ถ้า Martijn เลือกที่จะเปิดอีกครั้งฉันก็ไม่รังเกียจ
Jean-François Fabre

4
รูปแบบของปัญหานี้ ได้แก่x or y in z, x and y in z, x != y and zและคนอื่น ๆ ไม่กี่ แม้ว่าคำถามนี้จะไม่เหมือนกับคำถามนี้ แต่ต้นตอก็เหมือนกันสำหรับคำถามทั้งหมด แค่อยากจะชี้ให้เห็นว่าในกรณีที่มีคนปิดคำถามว่าซ้ำกับสิ่งนี้และไม่แน่ใจว่ามันเกี่ยวข้องกับพวกเขาอย่างไร
Aran-Fey

คำตอบ:


155

ในหลาย ๆ กรณี Python มีลักษณะและพฤติกรรมเหมือนภาษาอังกฤษตามธรรมชาติ แต่นี่เป็นกรณีหนึ่งที่นามธรรมล้มเหลว ผู้คนสามารถใช้เบาะแสบริบทเพื่อระบุว่า "Jon" และ "Inbar" เป็นอ็อบเจ็กต์ที่เชื่อมต่อกับคำกริยา "equals" แต่ตัวแปล Python มีความเข้าใจตามตัวอักษรมากกว่า

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

มีเหตุผลเทียบเท่ากับ:

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

ซึ่งสำหรับผู้ใช้ Bob เทียบเท่ากับ:

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

ตัวorดำเนินการเลือกอาร์กิวเมนต์แรกที่มีค่าความจริงเป็นบวก:

if ("Jon"):

และเนื่องจาก "จอน" มีค่าความจริงเป็นบวกifบล็อกจึงดำเนินการ นั่นคือสาเหตุที่ทำให้ "Access ได้รับ" ถูกพิมพ์โดยไม่คำนึงถึงชื่อที่กำหนด

การให้เหตุผลทั้งหมดนี้ใช้กับสำนวน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, และde


มีเหตุผลเฉพาะในการเลือกทูเพิล("Kevin", "Jon", "Inbar")แทนเซต{"Kevin", "Jon", "Inbar"} หรือไม่?
มนุษย์

2
ไม่จริงเนื่องจากทั้งสองใช้งานได้หากค่าทั้งหมดสามารถแก้ไขได้ การทดสอบสมาชิกชุดมีความซับซ้อนของ big-O ที่ดีกว่าการทดสอบสมาชิกแบบทูเพิล แต่การสร้างชุดนั้นมีราคาแพงกว่าการสร้างทูเพิลเล็กน้อย ฉันคิดว่าส่วนใหญ่เป็นการล้างคอลเล็กชั่นเล็ก ๆ เช่นนี้ เล่นกับเวลาa in {b, c, d}มันเร็วกว่าa in (b, c, d)บนเครื่องประมาณสองเท่า สิ่งที่ต้องพิจารณาว่านี่เป็นโค้ดที่สำคัญต่อประสิทธิภาพหรือไม่
Kevin

3
Tuple หรือ list เมื่อใช้ 'in' ในประโยค 'if'? แนะนำให้ตั้งค่าตัวอักษรสำหรับการทดสอบการเป็นสมาชิก ฉันจะอัปเดตโพสต์ของฉัน
Kevin

ใน Python สมัยใหม่มันรับรู้ว่าเซตนั้นเป็นค่าคงที่และทำให้เป็นค่าfrozensetแทนดังนั้นจึงไม่มีค่าโสหุ้ยการสร้าง dis.dis(compile("1 in {1, 2, 3}", '<stdin>', 'eval'))
endolith

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 == "เควิน"
  • "จอน"
  • "Inbar"

และถ้าคำสั่งนี้เทียบเท่ากับ

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.")
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.