Hãy xem xét vấn đề đơn giản này:
class Number:
def __init__(self, number):
self.number = number
n1 = Number(1)
n2 = Number(1)
n1 == n2 # False -- oops
Vì vậy, Python theo mặc định sử dụng các định danh đối tượng cho các hoạt động so sánh:
id(n1) # 140400634555856
id(n2) # 140400634555920
Ghi đè __eq__
chức năng dường như để giải quyết vấn đề:
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return False
n1 == n2 # True
n1 != n2 # True in Python 2 -- oops, False in Python 3
Trong Python 2 , luôn luôn nhớ ghi đè __ne__
hàm, như tài liệu nêu:
Không có mối quan hệ ngụ ý giữa các nhà khai thác so sánh. Sự thật x==y
không ngụ ý đó x!=y
là sai. Theo đó, khi xác định __eq__()
, người ta cũng nên xác định __ne__()
để các toán tử sẽ hành xử như mong đợi.
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
return not self.__eq__(other)
n1 == n2 # True
n1 != n2 # False
Trong Python 3 , điều này không còn cần thiết nữa, vì tài liệu nêu rõ:
Theo mặc định, __ne__()
các đại biểu đến __eq__()
và đảo ngược kết quả trừ khi có NotImplemented
. Không có mối quan hệ ngụ ý nào khác giữa các toán tử so sánh, ví dụ, sự thật (x<y or x==y)
không ngụ ý x<=y
.
Nhưng điều đó không giải quyết được tất cả các vấn đề của chúng tôi. Hãy thêm một lớp con:
class SubNumber(Number):
pass
n3 = SubNumber(1)
n1 == n3 # False for classic-style classes -- oops, True for new-style classes
n3 == n1 # True
n1 != n3 # True for classic-style classes -- oops, False for new-style classes
n3 != n1 # False
Lưu ý: Python 2 có hai loại lớp:
phong cách cổ điển (hoặc kiểu cũ ) lớp học, mà không kế thừa từobject
và được khai báo làclass A:
,class A():
hoặcclass A(B):
nơiB
là một lớp phong cách cổ điển;
các lớp kiểu mới , được kế thừa từobject
đó và được khai báo làclass A(object)
hoặcclass A(B):
ở đâuB
là một lớp kiểu mới. Python 3 chỉ có các lớp kiểu mới được khai báo làclass A:
,class A(object):
hoặcclass A(B):
.
Đối với các lớp kiểu cổ điển, một phép toán so sánh luôn gọi phương thức của toán hạng đầu tiên, trong khi đối với các lớp kiểu mới, nó luôn gọi phương thức của toán hạng lớp con, bất kể thứ tự của toán hạng .
Vì vậy, ở đây, nếu Number
là một lớp theo phong cách cổ điển:
n1 == n3
các cuộc gọi n1.__eq__
;
n3 == n1
các cuộc gọi n3.__eq__
;
n1 != n3
các cuộc gọi n1.__ne__
;
n3 != n1
các cuộc gọi n3.__ne__
.
Và nếu Number
là một lớp kiểu mới:
- cả hai
n1 == n3
và n3 == n1
gọi n3.__eq__
;
- cả hai
n1 != n3
và n3 != n1
gọi n3.__ne__
.
Để khắc phục sự cố không giao hoán của các toán tử ==
và !=
toán tử cho các lớp kiểu cổ điển Python 2, các phương thức __eq__
và __ne__
sẽ trả về NotImplemented
giá trị khi loại toán hạng không được hỗ trợ. Các tài liệu hướng dẫn xác định NotImplemented
giá trị như:
Các phương thức số và phương thức so sánh phong phú có thể trả về giá trị này nếu chúng không thực hiện thao tác cho các toán hạng được cung cấp. (Trình thông dịch sau đó sẽ thử hoạt động được phản ánh hoặc một số dự phòng khác, tùy thuộc vào toán tử.) Giá trị thật của nó là đúng.
Trong trường hợp này, toán tử ủy nhiệm thao tác so sánh cho phương thức phản ánh của toán hạng khác . Các tài liệu định nghĩa phản ánh phương pháp như:
Không có phiên bản đối số hoán đổi của các phương thức này (được sử dụng khi đối số bên trái không hỗ trợ thao tác nhưng đối số bên phải thì không); đúng hơn, __lt__()
và __gt__()
là sự phản ánh của nhau, __le__()
và __ge__()
là sự phản ánh của nhau,
__eq__()
và __ne__()
là sự phản ánh của chính họ.
Kết quả trông như thế này:
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return NotImplemented
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
x = self.__eq__(other)
if x is NotImplemented:
return NotImplemented
return not x
Trả về NotImplemented
giá trị thay vì False
là điều nên làm ngay cả đối với các lớp kiểu mới nếu tính giao hoán của toán tử ==
và !=
toán tử được mong muốn khi các toán hạng có kiểu không liên quan (không có kế thừa).
Chúng ta đã ở đó chưa? Không hẳn. Chúng ta có bao nhiêu số duy nhất?
len(set([n1, n2, n3])) # 3 -- oops
Các bộ sử dụng hàm băm của các đối tượng và theo mặc định Python trả về hàm băm của mã định danh của đối tượng. Hãy thử ghi đè lên nó:
def __hash__(self):
"""Overrides the default implementation"""
return hash(tuple(sorted(self.__dict__.items())))
len(set([n1, n2, n3])) # 1
Kết quả cuối cùng trông như thế này (tôi đã thêm một số xác nhận ở cuối để xác thực):
class Number:
def __init__(self, number):
self.number = number
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(other, Number):
return self.number == other.number
return NotImplemented
def __ne__(self, other):
"""Overrides the default implementation (unnecessary in Python 3)"""
x = self.__eq__(other)
if x is not NotImplemented:
return not x
return NotImplemented
def __hash__(self):
"""Overrides the default implementation"""
return hash(tuple(sorted(self.__dict__.items())))
class SubNumber(Number):
pass
n1 = Number(1)
n2 = Number(1)
n3 = SubNumber(1)
n4 = SubNumber(4)
assert n1 == n2
assert n2 == n1
assert not n1 != n2
assert not n2 != n1
assert n1 == n3
assert n3 == n1
assert not n1 != n3
assert not n3 != n1
assert not n1 == n4
assert not n4 == n1
assert n1 != n4
assert n4 != n1
assert len(set([n1, n2, n3, ])) == 1
assert len(set([n1, n2, n3, n4])) == 2
is
toán tử để phân biệt danh tính đối tượng với so sánh giá trị.