4.10.3 比较篇
在Python中,我们经常会比较两个数据结构是否相等,或者比较大小。对于Python内置的数据结构,这些数据结构的比较都是有着良好定义的。
用户自己写的数据结构,有时候也需要进行相互比较。而比较的逻辑实现就是通过魔术方法来完成的。
在Python中,如果自定义的类中没有写比较逻辑的时候,默认用is进行比较。
from icecream import ic
class Date:
def __init__(self, year, month, date):
self.year = year
self.month = month
self.date = date
x = Date(2022, 2, 22)
y = Date(2022, 2, 22)
ic(x == y) # 相当于:ic(x is y)
16:34:19|> x == y: False
4.10.3.1 eq、ne
判断两个对象是否相等时“==”会调用这个方法。
from icecream import ic
class Date:
def __init__(self, year, month, date):
self.year = year
self.month = month
self.date = date
def __eq__(self, other):
return self.year == other.year and self.month == other.month and self.date == other.date
x = Date(2022, 2, 22)
y = Date(2022, 2, 22)
ic(x == y)
16:39:20|> x == y: True
Python如果发现在自建类中没有定义不等于方法,那么会将等于方法取反作为不等于的返回值。
from icecream import ic
class Date:
def __init__(self, year, month, date):
self.year = year
self.month = month
self.date = date
def __eq__(self, other):
print('__eq__')
return self.year == other.year and self.month == other.month and self.date == other.date
x = Date(2022, 2, 22)
y = Date(2022, 11, 22)
ic(x != y)
eq
16:50:37|> x != y: True
如果定义了不等于方法,那么在判断不等于时,只会调用不等于方法。
from icecream import ic
class Date:
def __init__(self, year, month, date):
self.year = year
self.month = month
self.date = date
def __eq__(self, other):
print('__eq__')
return self.year == other.year and self.month == other.month and self.date == other.date
def __ne__(self, other):
print('__ne__')
return self.year != other.year or self.month != other.month or self.date != other.date
x = Date(2022, 2, 22)
y = Date(2022, 11, 22)
ic(x != y)
ne
16:53:01|> x != y: True
一般情况下,我们只需要在自建类中定义一个__eq__方法即可。
在__eq__和__ne__方法中,都需要提供另一个需要比较的对象:other,这是因为这2个方法都是用来进行二元比较的。
但我们在判断x是否和y相等时:x == y,其实是在判断:x.eq(y)。y会被当成other传入到x的__eq__方法中。
4.10.3.2 gt、lt
等于、不等于是有默认实现的,如果不定义,默认会用is进行判断。但是大于、小于如果没有定义,直接比较是会报错的。
from icecream import ic
class Date:
def __init__(self, year, month, date):
self.year = year
self.month = month
self.date = date
def __eq__(self, other):
print('__eq__')
return self.year == other.year and self.month == other.month and self.date == other.date
def __ne__(self, other):
print('__ne__')
return self.year != other.year or self.month != other.month or self.date != other.date
def __gt__(self, other):
if self.year > other.year:
return True
if self.year == other.year:
if self.month > other.month:
return True
if self.month == other.month:
return self.date > other.date
x = Date(2023, 2, 22)
y = Date(2022, 11, 22)
ic(x > y)
17:08:33|> x > y: True
这里虽然我们只定义了大于方法,但是我们此时可以直接使用小于号,因为Python默认大于号和小于号是一对。当不做特殊说明的时候,x > y就意味着y < x。如果判断x < y,在x中没有找到小于方法,就会去找y中的大于方法,这样也能进行判断。
4.10.3.3 ge、le
Python中不会对小于等于、大于等于进行推测,也就是,大于等于是一个整体,而非大于或等于。在没有定义大于等于时,如果直接用大于等于判断时会报错,而非调用大于、等于后进行or逻辑运算。
from icecream import ic
class Date:
def __init__(self, year, month, date):
self.year = year
self.month = month
self.date = date
def __ge__(self, other):
print('__ge__')
if self.year > other.year:
return True
if self.year == other.year:
if self.month > other.month:
return True
if self.month == other.month:
return self.date >= other.date
def __le__(self, other):
print('__le__')
if self.year < other.year:
return True
if self.year == other.year:
if self.month < other.month:
return True
if self.month == other.month:
return self.date <= other.date
x = Date(2023, 2, 22)
y = Date(2022, 11, 22)
ic(x >= y)
ge
17:16:13|> x >= y: True
4.10.3.4 hash
在Python中,每个自定义类都会默认有个__hash__方法进行绑定。如果定义了__eq__方法,那么这个默认的__hash__方法就会被删除掉。这个自定义类就会变得不可hash了。
原因是,在Python中,如果两个对象相等,那么这两个对象的hash值也必须相等。于是,我们必须在定义__eq__方法后再自行定义__hash__方法。
__hash__方法的要求有:
1、必须返回一个整数
2、对于两个相等的对象,hash值也必须相同
最好使用Python中自带的hash函数进行求值,如下:
from icecream import ic
class Date:
def __init__(self, year, month, date):
self.year = year
self.month = month
self.date = date
def __eq__(self, other):
return self.year == other.year and self.month == other.month and self.date == other.date
def __hash__(self):
return hash((self.year, self.month, self.date))
x = Date(2023, 2, 22)
y = Date(2023, 2, 22)
dct = {x: 999, y: 999}
ic(dct)
17:27:37|> dct: {<main.Date object at 0x000001A6C49EBEE0>: 999}
4.10.3.5 bool
对于我们所有的自定义对象,如果直接作为if语句的判断条件,都是返回True。如果想改变自定义对象在做boolean运算时候的结果,那么可以使用__bool__这个魔法方法。
from icecream import ic
class Date:
def __init__(self, year, month, date):
self.year = year
self.month = month
self.date = date
def __bool__(self):
print('bool')
return False
x = Date(2023, 2, 22)
ic(bool(x))
if not x:
ic(x)
bool
bool
ic| bool(x): False
ic| x: <main.Date object at 0x0000026ECCC633A0>