__Eq__ được xử lý như thế nào trong Python và theo thứ tự nào?


98

Vì Python không cung cấp các phiên bản trái / phải của các toán tử so sánh của nó, làm cách nào để nó quyết định gọi hàm nào?

class A(object):
    def __eq__(self, other):
        print "A __eq__ called"
        return self.value == other
class B(object):
    def __eq__(self, other):
        print "B __eq__ called"
        return self.value == other

>>> a = A()
>>> a.value = 3
>>> b = B()
>>> b.value = 4
>>> a == b
"A __eq__ called"
"B __eq__ called"
False

Điều này dường như gọi cả hai __eq__chức năng.

Tôi đang tìm cây quyết định chính thức.

Câu trả lời:


119

Các a == bbiểu hiện gọi A.__eq__, vì nó tồn tại. Mã của nó bao gồm self.value == other. Vì int không biết cách so sánh chính mình với B, Python cố gắng gọi B.__eq__để xem liệu nó có biết cách so sánh chính nó với int hay không.

Nếu bạn sửa đổi mã của mình để hiển thị những giá trị nào đang được so sánh:

class A(object):
    def __eq__(self, other):
        print("A __eq__ called: %r == %r ?" % (self, other))
        return self.value == other
class B(object):
    def __eq__(self, other):
        print("B __eq__ called: %r == %r ?" % (self, other))
        return self.value == other

a = A()
a.value = 3
b = B()
b.value = 4
a == b

nó sẽ in:

A __eq__ called: <__main__.A object at 0x013BA070> == <__main__.B object at 0x013BA090> ?
B __eq__ called: <__main__.B object at 0x013BA090> == 3 ?

69

Khi Python2.x thấy a == b, nó sẽ thử như sau.

  • Nếu type(b)là một lớp kiểu mới, và type(b)là một lớp con của type(a)type(b)đã được ghi đè __eq__, thì kết quả là b.__eq__(a).
  • Nếu type(a)có ghi đè __eq__(nghĩa type(a).__eq__là không object.__eq__), thì kết quả là a.__eq__(b).
  • Nếu type(b)đã ghi đè __eq__, thì kết quả là b.__eq__(a).
  • Nếu không có trường hợp nào ở trên, Python lặp lại quá trình đang tìm kiếm __cmp__. Nếu nó tồn tại, các đối tượng bằng nhau mà nó trả về zero.
  • Như một dự phòng cuối cùng, các cuộc gọi Python object.__eq__(a, b), là Trueiff ablà cùng một đối tượng.

Nếu bất kỳ phương thức đặc biệt nào trả về NotImplemented, Python sẽ hoạt động như thể phương thức đó không tồn tại.

Lưu ý bước cuối cùng một cách cẩn thận: nếu không ahoặc bquá tải ==, thì a == btương tự như a is b.


Từ https://eev.ee/blog/2012/03/24/python-faq-equality/


1
Có vẻ như 3 tài liệu python không chính xác. Xem bug.python.org/issue4395 và bản vá để làm rõ. TLDR: lớp con vẫn được so sánh đầu tiên, ngay cả khi nó ở trên rhs.
tối đa

Chào kev, bài viết hay. Bạn có thể giải thích nơi ghi dấu đầu dòng đầu tiên và tại sao nó được thiết kế như vậy?
wim,

1
Đúng, tài liệu này được ghi lại ở đâu cho python 2? Nó có phải là một PEP không?
Mr_and_Mrs_D

dựa trên câu trả lời này và các bình luận kèm theo, điều này chỉ khiến tôi bối rối hơn trước.
Sajuuk

và btw, có phải là định nghĩa một phương thức liên kết __eq__ chỉ trên trường hợp của một số kiểu không đủ để == bị ghi đè?
Sajuuk

5

Tôi đang viết một câu trả lời cập nhật cho Python 3 cho câu hỏi này.

Làm thế nào được __eq__xử lý trong Python và theo thứ tự nào?

a == b

Nó được hiểu một cách tổng quát, nhưng không phải lúc nào cũng vậy, nó a == bdẫn đến a.__eq__(b), hoặc type(a).__eq__(a, b).

Rõ ràng, thứ tự đánh giá là:

  1. if b's type là một lớp con nghiêm ngặt (không cùng kiểu) của a' s type và có một __eq__, gọi nó và trả về giá trị nếu so sánh được thực hiện,
  2. khác, nếu a__eq__, hãy gọi nó và trả lại nó nếu so sánh được triển khai,
  3. khác, hãy xem nếu chúng tôi không gọi b __eq__và nó có nó, sau đó gọi và trả về nó nếu so sánh được thực hiện,
  4. khác, cuối cùng, thực hiện so sánh cho danh tính, so sánh tương tự như is.

Chúng tôi biết nếu một so sánh không được triển khai nếu phương thức trả về NotImplemented.

(Trong Python 2, có một __cmp__phương thức đã được tìm kiếm, nhưng nó không được chấp nhận và bị loại bỏ trong Python 3)

Hãy tự mình kiểm tra hành vi của séc đầu tiên bằng cách cho B phân lớp A, điều này cho thấy rằng câu trả lời được chấp nhận là sai trong số này:

class A:
    value = 3
    def __eq__(self, other):
        print('A __eq__ called')
        return self.value == other.value

class B(A):
    value = 4
    def __eq__(self, other):
        print('B __eq__ called')
        return self.value == other.value

a, b = A(), B()
a == b

mà chỉ in B __eq__ calledtrước khi trả lại False.

Làm sao chúng ta biết được thuật toán đầy đủ này?

Các câu trả lời khác ở đây có vẻ chưa đầy đủ và lỗi thời, vì vậy tôi sẽ cập nhật thông tin chỉ cho bạn cách bạn có thể tự tra cứu vấn đề này.

Điều này được xử lý ở mức C.

Chúng ta cần xem xét hai bit mã khác nhau ở đây - mặc định __eq__cho các đối tượng của lớp objectvà mã tra cứu và gọi __eq__phương thức bất kể nó sử dụng mặc định __eq__hay tùy chỉnh.

Mặc định __eq__

Tra __eq__cứu trong tài liệu C api có liên quan cho chúng ta thấy rằng nó __eq__được xử lý bởi tp_richcompare- mà trong "object"định nghĩa kiểu trong cpython/Objects/typeobject.cđược định nghĩa trong object_richcomparecho case Py_EQ:.

    case Py_EQ:
        /* Return NotImplemented instead of False, so if two
           objects are compared, both get a chance at the
           comparison.  See issue #1393. */
        res = (self == other) ? Py_True : Py_NotImplemented;
        Py_INCREF(res);
        break;

Vì vậy, ở đây, nếu self == otherchúng ta trả về True, nếu chúng ta trả về NotImplementedđối tượng khác. Đây là hành vi mặc định cho bất kỳ lớp con nào của đối tượng không triển khai __eq__phương thức riêng của nó .

Làm thế nào __eq__được gọi là

Sau đó, chúng tôi tìm tài liệu API C, hàm PyObject_RichCompare , gọi do_richcompare.

Sau đó, chúng ta thấy rằng tp_richcomparehàm, được tạo cho "object"định nghĩa C được gọi bởi do_richcompare, vì vậy hãy xem xét kỹ hơn một chút.

Kiểm tra đầu tiên trong hàm này là các điều kiện mà các đối tượng được so sánh:

  • không cùng loại, nhưng
  • loại thứ hai là một lớp con của loại thứ nhất và
  • loại thứ hai có một __eq__phương thức,

sau đó gọi phương thức kia với các đối số được hoán đổi, trả về giá trị nếu được thực thi. Nếu phương pháp đó không được triển khai, chúng tôi tiếp tục ...

    if (!Py_IS_TYPE(v, Py_TYPE(w)) &&
        PyType_IsSubtype(Py_TYPE(w), Py_TYPE(v)) &&
        (f = Py_TYPE(w)->tp_richcompare) != NULL) {
        checked_reverse_op = 1;
        res = (*f)(w, v, _Py_SwappedOp[op]);
        if (res != Py_NotImplemented)
            return res;
        Py_DECREF(res);

Tiếp theo, chúng ta xem liệu chúng ta có thể tra cứu __eq__phương thức từ kiểu đầu tiên hay không và gọi nó. Miễn là kết quả không phải là NotImplemented, nghĩa là nó được thực hiện, chúng tôi trả lại nó.

    if ((f = Py_TYPE(v)->tp_richcompare) != NULL) {
        res = (*f)(v, w, op);
        if (res != Py_NotImplemented)
            return res;
        Py_DECREF(res);

Nếu không, nếu chúng tôi không thử phương thức của loại khác và nó ở đó, thì chúng tôi sẽ thử nó và nếu so sánh được thực hiện, chúng tôi trả về nó.

    if (!checked_reverse_op && (f = Py_TYPE(w)->tp_richcompare) != NULL) {
        res = (*f)(w, v, _Py_SwappedOp[op]);
        if (res != Py_NotImplemented)
            return res;
        Py_DECREF(res);
    }

Cuối cùng, chúng tôi nhận được một dự phòng trong trường hợp nó không được triển khai cho một trong hai loại của một người.

Dự phòng kiểm tra danh tính của đối tượng, nghĩa là nó có phải là cùng một đối tượng tại cùng một vị trí trong bộ nhớ hay không - đây là cách kiểm tra tương tự như đối với self is other:

    /* If neither object implements it, provide a sensible default
       for == and !=, but raise an exception for ordering. */
    switch (op) {
    case Py_EQ:
        res = (v == w) ? Py_True : Py_False;
        break;

Phần kết luận

Để so sánh, chúng tôi tôn trọng việc triển khai lớp con của so sánh trước tiên.

Sau đó, chúng tôi thử so sánh với việc triển khai đối tượng đầu tiên, sau đó với đối tượng thứ hai nếu nó không được gọi.

Cuối cùng, chúng tôi sử dụng một bài kiểm tra nhận dạng để so sánh cho sự bình đẳng.

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.