Tại sao trong Python, “0, 0 == (0, 0)” bằng “(0, False)”?


118

Trong Python (tôi chỉ kiểm tra với Python 3.6 nhưng tôi tin rằng nó cũng nên giữ cho nhiều phiên bản trước đó):

(0, 0) == 0, 0   # results in a two element tuple: (False, 0)
0, 0 == (0, 0)   # results in a two element tuple: (0, False)
(0, 0) == (0, 0) # results in a boolean True

Nhưng:

a = 0, 0
b = (0, 0)
a == b # results in a boolean True

Tại sao kết quả lại khác nhau giữa hai cách tiếp cận? Toán tử bình đẳng có xử lý các bộ giá trị khác nhau không?

Câu trả lời:


156

Cả hai biểu thức đầu tiên đều phân tích cú pháp dưới dạng bộ giá trị:

  1. (0, 0) == 0(là False), tiếp theo là0
  2. 0, tiếp theo là 0 == (0, 0)(vẫn Falselà theo cách đó).

Các biểu thức được phân chia theo cách đó do mức độ ưu tiên tương đối của dấu phân cách dấu phẩy so với toán tử bình đẳng: Python nhìn thấy một bộ chứa hai biểu thức, một trong số đó là một bài kiểm tra bình đẳng, thay vì một bài kiểm tra bình đẳng giữa hai bộ giá trị.

Nhưng trong tập hợp các câu lệnh thứ hai của bạn, a = 0, 0 không thể là một bộ. Một bộ giá trị là một tập hợp các giá trị và không giống như một bài kiểm tra bình đẳng, phép gán không có giá trị trong Python. Một phép gán không phải là một biểu thức, mà là một câu lệnh; nó không có giá trị có thể được đưa vào một bộ giá trị hoặc bất kỳ biểu thức xung quanh nào khác. Nếu bạn đã thử một cái gì đó như (a = 0), 0để buộc diễn giải dưới dạng một tuple, bạn sẽ gặp lỗi cú pháp. Điều đó khiến cho việc gán một bộ giá trị cho một biến - có thể được thực hiện rõ ràng hơn bằng cách viết nó a = (0, 0)- như một cách diễn giải hợp lệ duy nhất a = 0, 0.

Vì vậy, ngay cả khi không có dấu ngoặc đơn trên phép gán a, cả nó và bđược gán giá trị (0,0), do a == bđó True.


17
Tôi muốn nói rằng toán tử dấu phẩy có mức độ ưu tiên thấp hơn sự bình đẳng, vì đánh giá bình đẳng trước toán tử dấu phẩy: sự bình đẳng có mức độ ưu tiên cao hơn toán tử dấu phẩy. Nhưng đây luôn là một nguồn gây nhầm lẫn; chỉ muốn chỉ ra rằng các nguồn khác có thể lật ngược tình thế.
tomsmeding

2
Thay vào đó, bạn có thể tránh nhầm lẫn giữa các chuyển tiếp thấp hơn / cao hơn bằng cách nói rằng ,liên kết ít chặt chẽ hơn ==.
amalloy

4
Dấu phẩy là không một nhà điều hành docs.python.org/3.4/faq/...
Chris_Rands

48
Các tài liệu có thể tuyên bố rằng tất cả những gì họ muốn, nhưng điều đó không quan trọng. Bạn có thể viết một trình phân tích cú pháp để mọi toán tử đều có được sản xuất của riêng mình và không có "quyền ưu tiên" rõ ràng ở bất kỳ đâu trong quá trình triển khai, nhưng điều đó không ngăn các đơn vị cú pháp đó là toán tử. Bạn có thể định nghĩa lại "toán tử" theo một số cách triển khai cụ thể , đó rõ ràng là những gì họ đã làm Trong Python, nhưng điều đó không thay đổi hàm ý của thuật ngữ. Dấu phẩy là một cách hiệu quả một nhà điều hành trong đó sản xuất các bộ. Ví dụ, tính toán tử của nó thể hiện ở cách mức độ ưu tiên tương đối của nó bị ảnh hưởng bởi dấu ngoặc đơn.
Mark Reed,

68

Những gì bạn thấy trong cả 3 trường hợp là kết quả của đặc điểm ngữ pháp của ngôn ngữ và cách các mã thông báo gặp trong mã nguồn được phân tích cú pháp để tạo cây phân tích cú pháp.

Xem qua mã cấp thấp này sẽ giúp bạn hiểu những gì xảy ra bên dưới. Chúng tôi có thể lấy các câu lệnh python này, chuyển đổi chúng thành mã byte và sau đó dịch ngược chúng bằng cách sử dụng dismô-đun:

Trường hợp 1: (0, 0) == 0, 0

>>> dis.dis(compile("(0, 0) == 0, 0", '', 'exec'))
  1           0 LOAD_CONST               2 ((0, 0))
              3 LOAD_CONST               0 (0)
              6 COMPARE_OP               2 (==)
              9 LOAD_CONST               0 (0)
             12 BUILD_TUPLE              2
             15 POP_TOP
             16 LOAD_CONST               1 (None)
             19 RETURN_VALUE

(0, 0)được so sánh đầu tiên với 0đầu tiên và được đánh giá với False. Một bộ tuple sau đó được xây dựng với kết quả này và cuối cùng 0, vì vậy bạn sẽ nhận được (False, 0).

Trường hợp 2: 0, 0 == (0, 0)

>>> dis.dis(compile("0, 0 == (0, 0)", '', 'exec'))
  1           0 LOAD_CONST               0 (0)
              3 LOAD_CONST               0 (0)
              6 LOAD_CONST               2 ((0, 0))
              9 COMPARE_OP               2 (==)
             12 BUILD_TUPLE              2
             15 POP_TOP
             16 LOAD_CONST               1 (None)
             19 RETURN_VALUE

Một tuple được xây dựng bằng 0phần tử đầu tiên. Đối với phần tử thứ hai, việc kiểm tra tương tự được thực hiện như trong trường hợp đầu tiên và được đánh giá False, vì vậy bạn nhận được (0, False).

Trường hợp 3: (0, 0) == (0, 0)

>>> dis.dis(compile("(0, 0) == (0, 0)", '', 'exec'))
  1           0 LOAD_CONST               2 ((0, 0))
              3 LOAD_CONST               3 ((0, 0))
              6 COMPARE_OP               2 (==)
              9 POP_TOP
             10 LOAD_CONST               1 (None)
             13 RETURN_VALUE

Ở đây, như bạn thấy, bạn chỉ cần so sánh hai (0, 0)bộ giá trị đó và quay trở lại True.


20

Một cách khác để giải thích vấn đề: Có thể bạn đã quen với các từ trong từ điển

{ "a": 1, "b": 2, "c": 3 }

và mảng ký tự

[ "a", "b", "c" ]

và chữ tuple

( 1, 2, 3 )

nhưng những gì bạn không nhận ra là, không giống như từ điển và các ký tự mảng, các dấu ngoặc đơn mà bạn thường thấy xung quanh một ký tự tuple không phải là một phần của cú pháp nghĩa đen . Cú pháp nghĩa đen cho các bộ giá trị chỉ là một chuỗi các biểu thức được phân tách bằng dấu phẩy:

1, 2, 3

(một "danh sách giải thích" bằng ngôn ngữ của ngữ pháp chính thức cho Python ).

Bây giờ, bạn mong đợi mảng chữ nào

[ 0, 0 == (0, 0) ]

để đánh giá? Điều đó có lẽ trông giống như nó phải giống như

[ 0, (0 == (0, 0)) ]

tất nhiên là đánh giá [0, False]. Tương tự, với một bộ chữ có dấu ngoặc đơn rõ ràng

( 0, 0 == (0, 0) )

không có gì đáng ngạc nhiên khi nhận được (0, False). Nhưng dấu ngoặc đơn là tùy chọn;

0, 0 == (0, 0)

là điều tương tự. Và đó là lý do tại sao bạn nhận được (0, False).


Nếu bạn đang thắc mắc tại sao các dấu ngoặc quanh một chữ tuple là tùy chọn, thì phần lớn là vì sẽ rất khó chịu khi phải viết các bài tập hủy cấu trúc theo cách đó:

(a, b) = (c, d) # meh
a, b = c, d     # better

17

Thêm một vài dấu ngoặc đơn xung quanh thứ tự các hành động được thực hiện có thể giúp bạn hiểu kết quả tốt hơn:

# Build two element tuple comprising of 
# (0, 0) == 0 result and 0
>>> ((0, 0) == 0), 0
(False, 0)

# Build two element tuple comprising of
# 0 and result of (0, 0) == 0 
>>> 0, (0 == (0, 0))
(0, False)

# Create two tuples with elements (0, 0) 
# and compare them
>>> (0, 0) == (0, 0) 
True

Dấu phẩy được sử dụng để phân tách các biểu thức (tất nhiên bằng cách sử dụng dấu ngoặc đơn, chúng ta có thể buộc các hành vi khác nhau). Khi xem các đoạn mã bạn đã liệt kê, dấu phẩy ,sẽ phân tách nó và xác định những biểu thức nào sẽ được đánh giá:

(0, 0) == 0 ,   0
#-----------|------
  expr 1      expr2

Tuple (0, 0)cũng có thể được chia nhỏ theo cách tương tự. Dấu phẩy phân tách hai biểu thức bao gồm các ký tự 0.


6

Trong phần đầu tiên, Python đang tạo ra một bộ hai thứ:

  1. Biểu thức (0, 0) == 0, đánh giáFalse
  2. Hằng số 0

Trong cái thứ hai thì ngược lại.


0

nhìn vào ví dụ này:

r = [1,0,1,0,1,1,0,0,0,1]
print(r==0,0,r,1,0)
print(r==r,0,1,0,1,0)

sau đó kết quả:

False 0 [1, 0, 1, 0, 1, 1, 0, 0, 0, 1] 1 0
True 0 1 0 1 0

sau đó so sánh chỉ thực hiện với số đầu tiên (0 và r) trong ví dụ.

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.