Python的魔法方法一
什么是魔法方法
魔法方法在python的官方文档里叫special method,而magic method这个词是没有出现过的,所谓的魔术方法,就是让用户课制化一个类的方式,顾名思义,定义在类里的一些方法,特点,就是这些方法的前后,都有两个下划线
new和init
这两个方法,是可以改从一个类创建一个对象的行为,也比较容易被搞混。
new是从一个class建立一个object的过程
init是有了这个object后,给这个object初始化的过程
举个例子
class A:
def __new__(cls):
print("new")
return super().__new__(cls)
def __init__(self):
print("init")
o = A()
结果
new
init
上面这个类,就是new和init都用了,你可以粗暴的理解成o = A()的时候,先把这个class作为argument传给了new函数,返回一个object,然后这个object作为变量,去调用init函数。所以说
new是有返回值的,就是那个object
init是没有返回值的
如果我们在建立这个object的时候,传入了一些参数,那么这个参数,即会被存在new里,也会被存在init里
class A:
def __new__(cls, x):
print("new")
return super().__new__(cls)
def __init__(self, x):
self.x = x
print("init")
o = A(1)
在我们的实际应用中,这个new函数,是用的相对不多的,如果你不需要客制化建立这个object的过程,你只需要初始化这个object,那你只需要用到这个init,那什么时候需要用到new呢,比如说完哦要做一个singleton(单例) class,也就是说,在建立一个class的object之前,我首先要判断一下,我首先要判断一下有没有其他的object已经被建立了,如果有,那我就不创建新的了,这里我们就是在客制化建立这个object的过程,才会需要用到new,包括一些跟meataclass相关的内容,也会用到new。
init函数传入的这个self,就是你要初始化的对象,也就是那个object,init函数就该基本上折腾这个self就可以了
del
这个del,可以理解成一个析构函数,当然,他不是,当这个对象被释放的时候,你想干点什么
class A:
def __new__(cls):
print("new")
return super().__new__(cls)
def __init__(self):
print("init")
def __del__(self):
print("del")
def hello(self, name):
print(f"hello{name}")
def bug(self, name):
print(f"bug{name}")
o = A()
o.hello("lisi")
o.bug("zhangsan")
结果
new
init
hellolisi
bugzhangsan
del
我们发现,当这个对象已经不在调用什么方法的时候,也就是引用数为0的时候,他就要被释放了,但是这个方法并不是那么的好用,因为在垃圾回收的时候,对象也可能会被释放,这个释放会发生在任何时候。
同时呢,python里还有一个del关键字,这两个完全不是一个东西哈,在python中,del o的时候,并不一定会触发del魔法函数。del o只是让这个对象少了一个引用
class A:
def __del__(self):
print("del")
o = A()
x = o
del o
print("finish")
结果
finish
del
我们faxing运行结果,是先打印的finish,而不是先打印del,也就是这个del o并没有触发del魔法函数,这是因为我们在做del o的时候,这个object身上还有一个x的引用,接下来这个例子更清晰一点。
class A:
def __del__(self):
print("del")
def hello(self):
print("hello")
o = A()
x = o
del o
print("finish")
x.hello()
结果
finish
hello
del
eq、ne
print([1, 2] == [1, 2])
print(2 > 5)
print('a' >= 'b')
结果
True
False
False
我们看上面的这些,用的都是内置的比较函数,但是如果我们要比较类呢
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
x = Date(2024, 3, 27)
y = Date(2024, 3, 27)
print(x == y)
结果是False
因为在这里的 == ,其实比较的是x和y的地址,在python中当你没有去写一个类的比较逻辑的时候,它默认比较两个object是否相等的方法是is,本质上是打印了一个x is y,那我们可以通过eq函数来重新定义类的相等。
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
# self就是x,other就是y
def __eq__(self, other):
print("eq")
return self.year == other.year and self.month == other.month and self.day == other.day
x = Date(2024, 3, 27)
y = Date(2024, 3, 27)
print(x == y)
print("------------")
print(x != y)
结果
eq
True
------------
eq
False
我们发现,这样不仅是 == 被eq改变了,连!=也被eq改变了,那是因为python会自动将eq的方法取反来获取!=的结果,所以!=也调用了eq方法.
看起来我们应该返回一个boolean,其实我们可以返回其他的东西,比如字符串,这是python灵活的地方。
当然了我们也可以自己定义不等于方法
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
# self就是x,other就是y
def __eq__(self, other):
print("eq")
return self.year == other.year and self.month == other.month and self.day == other.day
def __ne__(self, other):
print("ne")
return self.year != other.year or self.month != other.month or self.day != other.day
x = Date(2024, 3, 27)
y = Date(2024, 3, 27)
print(x == y)
print("------------")
print(x != y)
结果
eq
True
------------
ne
False
gt、lt
上面的这些,x和y都是对称的,也就是可以用等于和不等于来判断的,那如果遇到了不对称的呢?需要用大于小于来比较的,我们知道等于和不等于是有默认实现的,但是大于小于可没有
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
# self就是x,other就是y
def __eq__(self, other):
print("eq")
return self.year == other.year and self.month == other.month and self.day == other.day
def __ne__(self, other):
print("ne")
return self.year != other.year or self.month != other.month or self.day != other.day
x = Date(2024, 3, 27)
y = Date(2024, 3, 27)
print(x > y)
会报错的
Traceback (most recent call last):
File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 19, in <module>
print(x > y)
TypeError: '>' not supported between instances of 'Date' and 'Date'
那我们就需要自己来实现这个功能
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
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.day > other.day
x = Date(2024, 3, 28)
y = Date(2024, 3, 27)
print(x > y)
print(x < y)
结果
True
False
我们发现,只实现了大于呀,怎么小于的也有了呢,因为x和y是同一种object,x>y就等于y>x;python会自己找的。当然了,我们也可以自己写小于的方法
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def __gt__(self, other):
print("gt")
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.day > other.day
def __lt__(self, other):
print("lt")
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.day < other.day
x = Date(2024, 3, 28)
y = Date(2024, 3, 27)
print(x > y)
print(x < y)
结果
gt
True
lt
False
那我们考虑一个问题,是因为我们实现了小于方法,所以就调用了lt吗
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def __gt__(self, other):
print("gt")
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.day > other.day
def __lt__(self, other):
print("lt")
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.day < other.day
class NewDate(Date):
pass
x = Date(2024, 3, 28)
y = NewDate(2024, 3, 27)
print(x < y)
结果
gt
False
NewDate继承自Date,什么都没做,当x<y的时候,其实执行判断的是y>x,而不是x<y
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def __str__(self):
# 通过str魔法函数,返回自身的信息
return f"{self.year}/{self.month}/{self.day} "
def __eq__(self, other):
print("eq")
# 在eq函数中按照顺序将self和other打印出来,在这里用到了上面的str函数
print(self, other)
return self.year == other.year and self.month == other.month and self.day == other.day
def __gt__(self, other):
print("gt")
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.day > other.day
def __lt__(self, other):
print("lt")
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.day < other.day
class NewDate(Date):
pass
x = Date(2024, 3, 28)
y = NewDate(2024, 3, 27)
print(x == y)
结果
eq
2024/3/27 2024/3/28
False
不对呀,为什么对比的顺序是 y==x呢,而不是x == y
而如果x和y都是一样的Object
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def __str__(self):
# 通过str魔法函数,返回自身的信息
return f"{self.year}/{self.month}/{self.day} "
def __eq__(self, other):
print("eq")
# 在eq函数中按照顺序将self和other打印出来
print(self, other)
return self.year == other.year and self.month == other.month and self.day == other.day
def __gt__(self, other):
print("gt")
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.day > other.day
def __lt__(self, other):
print("lt")
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.day < other.day
class NewDate(Date):
pass
x = Date(2024, 3, 28)
y = Date(2024, 3, 27)
print(x == y)
结果
eq
2024/3/28 2024/3/27
False
综合上面两个例子来看,如果x和y不是一个object,你就可能会遇到一些麻烦,这里的规则是,如果y是x的衍生类,那就优先使用y的比较方法,否则就优先使用x的比较方法,大部分情况下,优先使用左面的那个类,如果左边的没有,再去找右边的那个类的函数,除非右边的类是左边的类的子类。
同时呢,我们需要注意一下,x<y or x=y并不等于x<=y,也就是说python并不会在你有gt和eq的时候,并不会自动推断出x<=y可以拆解为gt和eq两个函数。
hash
我们有可能想去求某一个数据结构的hash值,一个自定义的数据结构,是有它默认的hash算法的。
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
x = Date(2024, 3, 28)
print(hash(x))
结果 272861618
我们最常见的hash的用法就是将一个数据结构的对象做为key,放到一些hashtable里,比如说字典比如说set
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def __repr__(self):
return f"{self.year}/{self.month}/{self.day}"
x = Date(2024, 3, 28)
y = Date(2024, 3, 28)
# 记录我每天赚了多少钱
income = {}
income[x] = 1000
income[y] = 1000
print(income)
结果
{2024/3/28: 1000, 2024/3/28: 1000}
我们发现啊,这个x和y虽然是一样的,但是在dict里,还是展示成了两个,其实根源是他们就是两个对象,那可能有的人想,简单啊,把上面的eq函数拿过来不就行了
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def __eq__(self, other):
return self.year == other.year and self.month == other.month and self.day == other.day
def __repr__(self):
return f"{self.year}/{self.month}/{self.day}"
x = Date(2024, 3, 28)
y = Date(2024, 3, 28)
# 记录我每天赚了多少钱
income = {}
income[x] = 1000
income[y] = 1000
print(income)
结果
Traceback (most recent call last):
File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 19, in <module>
income[x] = 1000
TypeError: unhashable type: 'Date'
报错了,说我们这个Data的类型是unhashable,也就是不可hash的,因为python虽然对每一个数据结构都默认了eq方法,以及一个hash函数,但是如果你对这个自定义类定义了自己的eq函数,这个默认的hash函数就会被删除掉,如果你还想对其使用hash方法,就必须自定义自己的hash函数。
hash的基本定义就是如果两个对象相等,他们的hash一定相等,当你改变了eq函数,他们的hash函数显然就不对了,所以如果我们还想要把这个类放在dict里的话,就必须在定义了新的eq的同时,也定义新的hash
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def __eq__(self, other):
return self.year == other.year and self.month == other.month and self.day == other.day
def __repr__(self):
return f"{self.year}/{self.month}/{self.day}"
def __hash__(self):
# 第一,必须返回一个整数
# 第二,对于两个相等的对象,必须有同样的一个hash值
# 第三,python推荐你使用官方提供的hash方法,通过把对象的核心属性组成一个tuple放到这个方法里,来返回你的对象的hash值
return hash((self.year, self.month, self.day))
x = Date(2024, 3, 28)
y = Date(2024, 3, 28)
# 记录我每天赚了多少钱
income = {}
income[x] = 1000
income[y] = 1000
print(income)
这样就是
{2024/3/28: 1000}
还有一个需要注意的是,如果你的对象是一个mutable(可变)的,就不应该作为key放在dict里,当然对于所有python object来说,都是mutable的,这里指的是这个类你创建了之后是不是还会变更。
bool
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
y = Date(2024, 3, 28)
# 当你把一个对象放在if statement里的时候,得到的都会是True
if y:
print("hello")
那怎么改变呢,就是说我怎么自己定义一个对象是True还是False呢
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def __repr__(self):
return f"{self.year}/{self.month}/{self.day}"
def __bool__(self):
print("bool")
# 一定要返回一个boolean值
return False
y = Date(2024, 3, 28)
print(bool(y))
if y:
print("hello")
结果
bool
False
bool