Tại sao `if none .__ eq __ (Hồi a)) dường như đánh giá là True (nhưng không hoàn toàn)?


146

Nếu bạn thực hiện câu lệnh sau trong Python 3.7, nó sẽ (từ thử nghiệm của tôi) in b:

if None.__eq__("a"):
    print("b")

Tuy nhiên, None.__eq__("a")đánh giá để NotImplemented.

Đương nhiên, "a".__eq__("a")đánh giá True"b".__eq__("a")đánh giá False.

Ban đầu tôi phát hiện ra điều này khi kiểm tra giá trị trả về của hàm, nhưng không trả về bất cứ thứ gì trong trường hợp thứ hai - vì vậy, hàm trả về None.

Những gì đang xảy ra ở đây?

Câu trả lời:


178

Đây là một ví dụ tuyệt vời về lý do tại sao các __dunder__phương thức không nên được sử dụng trực tiếp vì chúng thường không thay thế phù hợp cho các toán tử tương đương của chúng; ==thay vào đó, bạn nên sử dụng toán tử để so sánh bằng, hoặc trong trường hợp đặc biệt này, khi kiểm tra None, sử dụng is(bỏ qua phần dưới của câu trả lời để biết thêm thông tin).

Bạn đã hoàn thành

None.__eq__('a')
# NotImplemented

Mà trả về NotImplementedkể từ khi các loại được so sánh là khác nhau. Xem xét một ví dụ khác trong đó hai đối tượng với các loại khác nhau đang được so sánh theo kiểu này, chẳng hạn như 1'a'. Làm (1).__eq__('a')cũng không đúng, và sẽ trở lại NotImplemented. Cách đúng để so sánh hai giá trị này cho sự bình đẳng sẽ là

1 == 'a'
# False

Những gì xảy ra ở đây là

  1. Đầu tiên, (1).__eq__('a')được thử, mà trả về NotImplemented. Điều này chỉ ra rằng hoạt động không được hỗ trợ, vì vậy
  2. 'a'.__eq__(1)được gọi, cũng trả lại tương tự NotImplemented. Vì thế,
  3. Các đối tượng được đối xử như thể chúng không giống nhau, và Falseđược trả lại.

Đây là một MCVE nhỏ xinh bằng cách sử dụng một số lớp tùy chỉnh để minh họa điều này xảy ra:

class A:
    def __eq__(self, other):
        print('A.__eq__')
        return NotImplemented

class B:
    def __eq__(self, other):
        print('B.__eq__')
        return NotImplemented

class C:
    def __eq__(self, other):
        print('C.__eq__')
        return True

a = A()
b = B()
c = C()

print(a == b)
# A.__eq__
# B.__eq__
# False

print(a == c)
# A.__eq__
# C.__eq__
# True

print(c == a)
# C.__eq__
# True

Tất nhiên, điều đó không giải thích tại sao hoạt động trở lại đúng. Điều này là bởi vì NotImplementedthực sự là một giá trị trung thực:

bool(None.__eq__("a"))
# True

Giống như,

bool(NotImplemented)
# True

Để biết thêm thông tin về những giá trị nào được coi là trung thực và giả mạo, hãy xem phần tài liệu về Kiểm tra giá trị thật , cũng như câu trả lời này . Điều đáng chú ý ở đây NotImplementedlà sự thật, nhưng nó sẽ là một câu chuyện khác nếu lớp xác định một __bool__hoặc __len__phương thức trả về Falsehoặc 0tương ứng.


Nếu bạn muốn chức năng tương đương của ==toán tử, sử dụng operator.eq:

import operator
operator.eq(1, 'a')
# False

Tuy nhiên, như đã đề cập trước đó, đối với kịch bản cụ thể này , nơi bạn đang kiểm tra None, hãy sử dụng is:

var = 'a'
var is None
# False

var2 = None
var2 is None
# True

Tương đương chức năng của điều này là sử dụng operator.is_:

operator.is_(var2, None)
# True

Nonelà một đối tượng đặc biệt và chỉ có 1 phiên bản tồn tại trong bộ nhớ tại bất kỳ thời điểm nào. IOW, nó là đơn lẻ duy nhất của NoneTypelớp (nhưng cùng một đối tượng có thể có bất kỳ số lượng tài liệu tham khảo nào). Các hướng dẫn PEP8 làm cho điều này rõ ràng:

Việc so sánh với các singletons như vậy Nonephải luôn luôn được thực hiện với ishoặc is not, không bao giờ là các toán tử đẳng thức.

Tóm lại, đối với các singletons như None, một kiểm tra tham chiếu với isphù hợp hơn, mặc dù cả hai ==issẽ hoạt động tốt.


33

Kết quả bạn đang thấy là do thực tế đó

None.__eq__("a") # evaluates to NotImplemented

đánh giá NotImplementedNotImplementedgiá trị thật của nó được ghi lại là True:

https://docs.python.org/3/l Library / constants.html

Giá trị đặc biệt mà nên được trả lại bằng các phương pháp đặc biệt nhị phân (ví dụ như __eq__(), __lt__(), __add__(), __rsub__(), vv) để cho biết rằng các hoạt động không được thực hiện đối với các loại khác với; có thể được trả lại bằng các phương pháp tại chỗ nhị phân đặc biệt (ví dụ như __imul__(), __iand__(), vv) cho cùng một mục đích. Giá trị thật của nó là đúng.

Nếu bạn gọi __eq()__phương thức bằng tay thay vì chỉ sử dụng ==, bạn cần chuẩn bị để đối phó với khả năng nó có thể trả về NotImplementedvà giá trị thật của nó là đúng.


16

Như bạn đã hình dung None.__eq__("a")đánh giá NotImplementedtuy nhiên nếu bạn thử một cái gì đó như

if NotImplemented:
    print("Yes")
else:
    print("No")

kết quả là

Đúng

điều này có nghĩa là giá trị thật của NotImplemented true

Do đó, kết quả của câu hỏi là rõ ràng:

None.__eq__(something) sản lượng NotImplemented

bool(NotImplemented)đánh giá là Đúng

Vì vậy, if None.__eq__("a")luôn luôn đúng


1

Tại sao?

Nó trả về a NotImplemented, yeah:

>>> None.__eq__('a')
NotImplemented
>>> 

Nhưng nếu bạn nhìn vào điều này:

>>> bool(NotImplemented)
True
>>> 

NotImplementedthực sự là một giá trị trung thực, vì vậy đó là lý do tại sao nó trở lại b, bất cứ điều gì Truesẽ qua, bất cứ điều gì Falsesẽ không xảy ra.

Làm thế nào để giải quyết nó?

Bạn phải kiểm tra nếu có True, vì vậy hãy nghi ngờ hơn, như bạn thấy:

>>> NotImplemented == True
False
>>> 

Vì vậy, bạn sẽ làm:

>>> if None.__eq__('a') == True:
    print('b')


>>> 

Và như bạn thấy, nó sẽ không trả lại bất cứ thứ gì.


1
câu trả lời rõ ràng nhất - v bổ sung đáng giá - cảm ơn bạn
scharfmn

1
:) “đáng giá bổ sung” không hoàn toàn nắm bắt được những gì tôi đã cố gắng để nói (như bạn OBV nhìn thấy) - có thể “muộn xuất sắc” là những gì tôi muốn - cổ vũ
scharfmn

@scharfmn có? Tôi tò mò muốn biết những gì bạn nghĩ câu trả lời này cho biết thêm chưa được đề cập trước đó.
cs95

bằng cách nào đó những thứ trực quan / thay thế ở đây thêm rõ ràng - bản demo đầy đủ
scharfmn

@scharfmn ... Mà câu trả lời được chấp nhận cũng có mặc dù các lời nhắc đã bị xóa. Bạn đã upvote chỉ bởi vì các lời nhắc thiết bị đầu cuối đã được lười biếng để lại trong?
cs95
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.