1、概念
魔法函数是python中以双下划线开始加函数名和双下划线结束的名称作为函数名的函数(如:__init__),一般使用python提供的魔法函数,使用魔法函数可以增强类的一些特性,隐式调用
#没有__getitem__魔法函数 class Company(object): def __init__(self,employee_list): self.employee = employee_list company = Company(["tom","bob","jane"]) employee = company.employee for em in employee: print(em)
1 #实现getitem魔法函数后 2 class Company(object): 3 def __init__(self,employee_list): 4 self.employee = employee_list 5 def __getitem__(self,item): 6 return self.employee[item] 7 company = Company(["tom","bob","jane"]) 8 for em in company: 9 print(em)
实现__getitem__魔法函数后,可以直接对对象进行遍历,而不需要获取对象中的列表来进行遍历。当循环对象时,python解释器会去调用getitem,对于item从0开始尝试,一直到抛出异常才会结束for循环。实现__getitem__后,将类和类实例化出来的对象变成一个可迭代的对象,在该例中,company本身是一个class类型,通过魔法函数加强了company,使得company成为一个可迭代的类型
注意:for循环实际使用中是要拿到对象的迭代器进行遍历,然而在company中没有迭代器(迭代器需要实现__iter__魔法函数才会有),python语法在此时做了一些优化,如果没有拿到迭代器,就会尝试获取__getitem__方法,如果有就一次次遍历,直到取完所有数据。实现getitem魔法函数后,对象就变成一个序列,可以进行切片操作,没实现则会报错
2、魔法函数对python的影响
定义魔法函数后,可以影响一些内置的函数,改变python的语法,如内置函数len()---返回字符串、列表、字典、元组等长度
在没有__len__魔法函数时,直接对对象使用len()方法,报错如下:
1 #__len__()魔法函数 2 class Company(object): 3 def __init__(self,employee_list): 4 self.employee = employee_list 5 def __len__(self): 6 return len(self.employee) 7 company = Company(["tom","bob","jane"]) 8 len(company) 9 ''' 10 输出结果 11 3 12 '''
直接使用len(company)会报错,而在给Company类添加__len__魔法函数后,就会正常返回
3、部分魔法函数使用
(1)__str__
当没有魔法函数__str__时,若打印一个对象,则会调用默认的str()方法,输出一个字符串
1 class Comoany(object): 2 def __init__(self,employee_list): 3 self.employee = employee_list 4 company = Comoany(["tom","bob","jane"]) 5 print(company) 6 ''' 7 输出结果 8 <__main__.Comoany object at 0x000002026F8A8470> 9 '''
添加魔法函数__str__
1 class Comoany(object): 2 def __init__(self,employee_list): 3 self.employee = employee_list 4 def __str__(self): 5 return ",".join(self.employee) 6 company = Comoany(["tom","bob","jane"]) 7 print(company) 8 ''' 9 输出结果 10 tom,bob,jane 11 '''
# 此时若直接用company则在开发人员的开发模式界面中仍会输出对象的地址,因为开发模式(python解释器或notebook)默认调用的是__repr__魔法函数,因此要输出正确结果,需要重写__repr__魔法函数,重写仍然是在开发人员模式下能看到结果,在pycharm中仍然没有输出。
(2)__repr__
没有魔法函数__repr__
1 class Comoany(object): 2 def __init__(self,employee_list): 3 self.employee = employee_list 4 def __str__(self): 5 return ",".join(self.employee) 6 company = Comoany(["tom","bob","jane"]) 7 company 8 ''' 9 开发模式下输出结果 10 <__main__.Company at 0x4bc5208> 11 '''
添加魔法函数__repr__
1 class Comoany(object): 2 def __init__(self,employee_list): 3 self.employee = employee_list 4 def __str__(self): 5 return ",".join(self.employee) 6 def __repr__(self): 7 return ",".join(self.employee) 8 company = Comoany(["tom","bob","jane"]) 9 company 10 ''' 11 开发模式下输出结果:tom,bob,jane 12 '''
小结:
__repr__是开发模式下调用
__str__是对对象进行字符串格式化时调用
魔法函数可以写到任何一个定义的类当中去,与继承的哪个类没有关系
魔法函数只需定义,Python解释器自己调用
(3)__abs__()
在常规的abs()使用方法中,必须传入数字,添加该魔法函数后,可以传入对象
1 class Nums(object): 2 def __init__(self, num): 3 self.num = num 4 def __abs__(self): 5 return abs(self.num) 6 my_num = Nums(-3) 7 abs(my_num) 8 ''' 9 输出结果 10 3 11 '''
(4)__add__
1 class MyVector(object): 2 def __init__(self, x, y): 3 self.x = x 4 self.y = y 5 print("####") 6 def __add__(self,other_instance): 7 print("2") 8 print(self.x) 9 re_vector = MyVector(self.x+other_instance.x,self.y+other_instance.y) 10 return re_vector 11 def __str__(self): 12 print("3") 13 return "x:{x},y:{y}".format(x = self.x,y = self.y) 14 first_vec = MyVector(1,2) 15 second_vec = MyVector(2,3) 16 print(first_vec + second_vec) 17 ''' 18 输出结果: 19 #### 20 #### 21 2 22 #### 23 3 24 x:3,y:5 25 '''
运行过程分析:first_vec = MyVector(1,2)和second_vec = MyVector(2,3)分别调用__init__魔法函数(共两次init方法),创建两个向量,first_vec + second_vec,将第一个当成self,第二个当成other_instance,所得结果当成一个新的MyVector对象返回(调用一次init方法),在print(first_vec + second_vec)中对对象进行打印操作,调用__str__函数,输出结果。只要有init方法,每创建一个对象都会调用init方法
4、魔法函数的重要性---通过len()函数说明
当对对象使用len()方法时,会隐含的去调用__len__魔法函数,实际上,len方法所做的工作,远不止这些,如果在计算列表的长度时,python为了加速自己的性能-----如果len方法用到内置的类型上,如list、dic、set(都是用c语言实现的),python会走一个捷径,在cpython中会直接读取list、dic、set的数据即列表的长度,而不会遍历数据,因此使用中尽量使用原生的数据结构。