Tại sao `a == b hoặc c hoặc d` luôn đánh giá là True?


108

Tôi đang viết một hệ thống bảo mật từ chối quyền truy cập của người dùng trái phép.

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

Nó cấp quyền truy cập cho người dùng được ủy quyền như mong đợi, nhưng nó cũng cho phép người dùng trái phép!

Hello. Please enter your name:
Bob
Access granted.

Tại sao điều này xảy ra? Tôi đã tuyên bố rõ ràng là chỉ cấp quyền truy cập khi namebằng Kevin, Jon hoặc Inbar. Tôi cũng đã thử logic ngược lại if "Kevin" or "Jon" or "Inbar" == name, nhưng kết quả là như nhau.


1
@ Jean-François FYI đã có một số cuộc thảo luận về câu hỏi này và mục tiêu dupe của nó trước đó trong phòng python, cuộc thảo luận bắt đầu tại đây . Tôi hiểu nếu bạn muốn đóng nó, nhưng tôi nghĩ rằng bạn có thể muốn biết về lý do mà bài viết đã được mở lại gần đây. Tiết lộ đầy đủ: Martijn, tác giả của câu trả lời về mục tiêu dupe vẫn chưa có thời gian để nói về vấn đề này.
Andras Deak

Câu trả lời của Martijn thật xuất sắc khi giải thích nó bằng cách "không sử dụng ngôn ngữ tự nhiên", những người khác, à, ... đó là những thời kỳ huy hoàng ... Câu trả lời dưới đây chỉ lặp lại điều này. Đối với tôi, nó là một bản sao. Nhưng nếu Martijn chọn mở lại, tôi không phiền.
Jean-François Fabre

4
Các biến thể của vấn đề này bao gồm x or y in z, x and y in z, x != y and zvà một vài người khác. Mặc dù không hoàn toàn giống với câu hỏi này, nhưng nguyên nhân gốc rễ là giống nhau đối với tất cả chúng. Chỉ muốn chỉ ra điều đó trong trường hợp có ai đó đóng câu hỏi của họ là trùng lặp với điều này và không chắc nó có liên quan đến họ như thế nào.
Aran-Fey

Câu trả lời:


152

Trong nhiều trường hợp, Python trông và hoạt động giống như tiếng Anh tự nhiên, nhưng đây là một trường hợp mà sự trừu tượng đó không thành công. Mọi người có thể sử dụng các manh mối ngữ cảnh để xác định rằng "Jon" và "Inbar" là các đối tượng được kết hợp với động từ "bằng", nhưng trình thông dịch Python có ý nghĩa hơn.

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

về mặt logic tương đương với:

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

Đối với người dùng Bob, tương đương với:

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

Các ornhà khai thác chọn đối số đầu tiên với một dương giá trị thật :

if ("Jon"):

Và vì "Jon" có giá trị sự thật dương, ifkhối sẽ thực thi. Đó là nguyên nhân khiến "Quyền truy cập được cấp" được in bất kể tên đã cho.

Tất cả suy luận này cũng áp dụng cho biểu thức if "Kevin" or "Jon" or "Inbar" == name. giá trị đầu tiên "Kevin", là true, vì vậy ifkhối sẽ thực thi.


Có hai cách phổ biến để tạo điều kiện này một cách chính xác.

  1. Sử dụng nhiều ==toán tử để kiểm tra rõ ràng từng giá trị:
    if name == "Kevin" or name == "Jon" or name == "Inbar":

  2. Soạn một chuỗi các giá trị hợp lệ và sử dụng intoán tử để kiểm tra tư cách thành viên:
    if name in {"Kevin", "Jon", "Inbar"}:

Nhìn chung, hai phần thứ hai nên được ưu tiên hơn vì nó dễ đọc hơn và cũng nhanh hơn:

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

Đối với những người có thể muốn bằng chứng if a == b or c or d or e: ...thực sự được phân tích cú pháp như thế này. Mô- astđun tích hợp cung cấp câu trả lời:

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

Vì vậy, testtrong những ifvẻ tuyên bố như thế này:

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

Như người ta có thể thấy, đó là các nhà điều hành boolean oráp dụng cho nhiều values, cụ thể là, a == bc, de.


Có lý do cụ thể nào để chọn một tuple ("Kevin", "Jon", "Inbar")thay vì một tập hợp {"Kevin", "Jon", "Inbar"} không?
Con người

2
Không thực sự, vì cả hai đều hoạt động nếu tất cả các giá trị đều có thể băm. Kiểm tra thành viên tập hợp có độ phức tạp lớn-O tốt hơn so với thử nghiệm thành viên tuple, nhưng việc xây dựng một tập hợp sẽ đắt hơn một chút so với việc xây dựng một tuple. Tôi nghĩ phần lớn là rửa cho những bộ sưu tập nhỏ như thế này. Chơi với timeit a in {b, c, d}nhanh gấp đôi so với a in (b, c, d)trên máy của tôi. Điều gì đó cần suy nghĩ nếu đây là một đoạn mã quan trọng về hiệu suất.
Kevin

3
Tuple hoặc liệt kê khi sử dụng 'in' trong mệnh đề 'if'? đề xuất đặt các chữ để kiểm tra tư cách thành viên. Tôi sẽ cập nhật bài viết của tôi.
Kevin

Trong Python hiện đại, nó nhận ra rằng tập hợp là một hằng số và làm cho nó trở thành một hằng số frozenset, vì vậy chi phí xây dựng tập hợp không có ở đó. dis.dis(compile("1 in {1, 2, 3}", '<stdin>', 'eval'))
endolith

1

Vấn đề kỹ thuật đơn giản, hãy đơn giản hơn một chút.

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

Tuy nhiên, kế thừa từ ngôn ngữ C, Python đánh giá giá trị logic của một số nguyên khác 0 là True.

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

Bây giờ, Python xây dựng dựa trên logic đó và cho phép bạn sử dụng các ký tự logic chẳng hạn như hoặc trên số nguyên, v.v.

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

Cuối cùng

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

Cách thích hợp để viết nó sẽ là:

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

Để đảm bảo an toàn, tôi cũng khuyên bạn không nên mã hóa mật khẩu cứng.


1

Có 3 điều kiện kiểm tra trong if name == "Kevin" or "Jon" or "Inbar":

  • tên == "Kevin"
  • "Jon"
  • "Trong quán bar"

và câu lệnh if này tương đương với

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

elif "Jon"sẽ luôn đúng nên quyền truy cập cho bất kỳ người dùng nào cũng được cấp

Giải pháp


Bạn có thể sử dụng bất kỳ phương pháp nào bên dưới

Nhanh

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

Chậm

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

Mã chậm + Không cần thiết

if name == "Kevin":
    print("Access granted.")
elif name == "Jon":
    print("Access granted.")
elif name == "Inbar":
    print("Access granted.")
else:
    print("Access denied.")
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.