Python中的Dunder/Magic方法
Dunder方法是指前面和后面都有双下划线的名字,因此被称为dunder。它们也被称为魔法方法,可以帮助覆盖自定义类的内置函数的功能。
1.简介
为类实现dunder方法是多态性的一种很好的形式。如果你曾经在Python中创建了一个类并使用了init函数,那么你就已经在使用dunder方法了。
2.先决条件
在我们继续之前,必须具备以下条件。
- 对使用Python的面向对象编程有基本了解。
- 有在Python中处理类的经验。
- 熟悉内置函数,如len、get、set等。
3.为什么我们需要Dunder方法?
考虑一个案例,我们有以下的类。
class point:
x = 4
y = 5
p1 = point()
print(p1)
打印语句会打印出类似<__main__.point object at 0x7fb992998d00> 的东西。但是,我们可能想让打印语句显示一些格式为(4,10) 。我们可以通过覆盖我们类的__str__ 方法来实现这个目的。
我们还可以覆盖其他的方法,如len, +, [] 等。我们将创建一个新的类并覆盖本文中的许多内置函数。
4.我们的自定义类
class softwares:
names = []
versions = {}
这个类将用于保存一个软件及其版本的列表。names 是一个存储软件名称的列表,versions 是一个字典,其中键是软件名称,值是版本号。默认情况下,所有的软件都以1的版本开始。
5.为我们的类提供Dunder方法
在继续前进之前,请确保你的缩进是正确的。下面要讨论的方法是属于我们创建的类的方法,必须适当地缩进。
5.1 init
如果你曾与类打过交道,这个方法你一定已经用过了。init 方法用于创建一个类的实例。
def __init__(self,names):
if names:
self.names = names.copy()
for name in names:
self.versions[name] = 1
else:
raise Exception("Please Enter the names")
上面定义的init 方法接受一个名字的列表作为参数,并将其存储在类的names 列表中。此外,它还填充了versions 字典。我们还在names 列表中设置了一个检查。
如果这个列表是空的,就会产生一个异常。下面是我们如何使用init 方法。
p = softwares(['S1','S2','S3'])
p1 = softwares([])
第一条语句可以正常工作,但第二行会引发一个异常,因为一个空的列表被作为参数传入。
5.2 str
当我们想在打印语句中使用我们类的实例时,str 方法很有用。正如前面所讨论的,它通常返回一个内存对象。但是我们可以重写str 方法以满足我们的要求。
def __str__(self):
s ="The current softwares and their versions are listed below: \n"
for key,value in self.versions.items():
s+= f"{key} : v{value} \n"
return s
上面的str 方法返回软件和它们的版本。确保该函数返回一个字符串。下面是我们如何调用该方法。
print(p)
5.3 Setitem
在字典中赋值时,setitem 方法被调用。
d = {}
d['key'] = value
我们可以在setitem 方法的帮助下给我们类的实例一个类似的功能。
def __setitem__(self,name,version):
if name in self.versions:
self.versions[name] = version
else:
raise Exception("Software Name doesn't exist")
上面的方法是要更新软件的版本号。如果没有找到该软件,它将引发一个错误。
在第3行中,我们使用字典内置的setitem 方法。
我们可以通过以下方式调用setitem 方法。
p['S1'] = 2
p['2'] = 2
第一行将更新软件S1的版本为2。但第二行将引发一个异常,因为软件2不存在。
5.4 getitem
getitem 方法和setitem 方法一样,主要的区别是,当我们使用字典的[] 操作符时,会调用getitem 方法。
d = {'val':key}
print(d['val'])
我们类的实例也可以被赋予类似的功能。
def __getitem__(self,name):
if name in self.versions:
return self.versions[name]
else:
raise Exception("Software Name doesn't exist")
上述方法实质上是返回软件的版本。如果没有找到该软件,它会引发一个异常。为了调用getitem 方法,我们可以写下面一行代码。
print(p['S1'])
print(p['1'])
第一行将打印S1的版本。但是,第二行会引发一个异常,因为1并不存在。
5.5 delitem
delitem ,就像setitem 和getitem 方法一样。为了避免重复,我们将继续讨论实现和用例。
def __delitem__(self,name):
if name in self.versions:
del self.versions[name]
self.names.remove(name)
else:
raise Exception("Software Name doesn't exist")
delitem 方法将软件从字典以及列表中删除。
它的使用方法如下。
del p['S1']
5.6 len
在 dictionary 中,len 方法返回 list 中的元素数或 dictionary 中的 key-value 对数。
我们也可以为我们的类定义一个len 方法。
def __len__(self):
return len(self.names)
我们类的len 方法返回软体的数量。你可能已经注意到了,我们正在使用列表中内置的len 方法来返回软件的数量。
我们类的len 方法可以用在以下方面。
print(len(p))
5.7 包含
contains 方法是在使用in 操作符时使用的。其返回值必须是一个布尔值。
def __contains__(self,name):
if name in self.versions:
return True
else:
return False
该方法检查名称是否在字典中找到。我们将为此使用字典内置的contains 方法。
if 'S2' in p:
print("Software Exists")
else:
print("Software DOESN'T exist")
上面的代码在if块内打印语句,因为软件S2存在于versions 字典内。
5.8完整的代码
class softwares:
names = []
versions = {}
def __init__(self,names):
if names:
self.names = names.copy()
for name in names:
self.versions[name] = 1
else:
raise Exception("Please Enter the names")
def __str__(self):
s ="The current softwares and their versions are listed below: \n"
for key,value in self.versions.items():
s+= f"{key} : v{value} \n"
return s
def __setitem__(self,name,version):
if name in self.versions:
self.versions[name] = version
else:
raise Exception("Software Name doesn't exist")
def __getitem__(self,name):
if name in self.versions:
return self.versions[name]
else:
raise Exception("Software Name doesn't exist")
def __delitem__(self,name):
if name in self.versions:
del self.versions[name]
self.names.remove(name)
else:
raise Exception("Software Name doesn't exist")
def __len__(self):
return len(self.names)
def __contains__(self,name):
if name in self.versions:
return True
else:
return False
6.一些更多的dunder方法
在看一些更多的dunder方法之前,让我们创建一个新的类。
class point:
x = None
y = None
def __init__(self, x , y):
self.x = x
self.y = y
def __str__(self):
s = f'({self.x},{self.y})'
return s
p1 = point(5,4)
p2 = point(2,3)
我们已经创建了一个点类,它基本上是一个二维的点。该类有一个init 方法和一个str 方法。我们还创建了该类的几个实例。
6.1 添加
当使用+ 操作符时,add 方法被调用。我们可以为我们的类定义一个自定义的add 方法。
p1 + p2 等于p1._add__(p2)
def __add__(self,p2):
x = self.x + p2.x
y = self.y + p2.y
return point(x,y)
上面的方法添加了point 的第一个实例和point 的第二个实例的x和y坐标。它将创建一个新的point 的实例,然后返回它。
p3 = p1 + p2
上面的这行代码调用了add 方法。
6.2 iadd
iadd 方法和add 方法一样。它在使用+= 操作符时被调用。
def __iadd__(self,p2):
self.x += p2.x
self.y += p2.y
return self
上面的方法只是通过添加p2 的坐标来更新一个实例的坐标。确保你返回self ,否则它将返回None,不会像预期的那样工作。
p1 += p2
print(p1)
上面的方法调用了iadd 方法。
6.3 其他运算符
__sub__(self,p2)( - )__isub__(self,p2)( -= )__mul__(self,p2)( * )__imul__(self,p2)( *= )__truediv__(self,p2)( \ )__itruediv__(self,p2)( \= )__floordiv__(self,p2)( \\ )__ifloordiv__(self,p2)( \= )
6.4 调用
当调用一个像func() 的函数时,我们是在调用call 方法。
如果我们为我们的类放置一个call 方法,我们就可以做以下事情。
p1()
p2()
下面是一个调用方法的例子。
def __call__(self):
print(f"Called Point {self.x},{self.y}")
6.5 完整的代码
class point:
x = None
y = None
def __init__(self, x , y):
self.x = x
self.y = y
def __str__(self):
s = f'({self.x},{self.y})'
return s
def __add__(self,p2):
print("In add")
x = self.x + p2.x
y = self.y + p2.y
return point(x,y)
def __iadd__(self,p2):
self.x += p2.x
self.y += p2.y
return self
def __isub__(self,p2):
self.x -= p2.x
self.y -= p2.y
return self
def __imul__(self,p2):
self.x *= p2.x
self.y *= p2.y
return self
def __itruediv__(self,p2):
self.x /= p2.x
self.y /= p2.y
return self
def __ifloordiv__(self,p2):
self.x //= p2.x
self.y //= p2.y
return self
def __call__(self):
print(f"Called Point {self.x},{self.y}")
7.总结
Dunder方法确实很神奇,可以帮助你提高你的类的功能。