魔法方法的官方名称为special method,指的是Python自带的,具有强大功能的,在一些特定情况下会被自动调用的方法,方法名一般以两个下划线开始,并且以两个下划线结束,Python中大致有100多个魔法方法
自动被调用的场景
具有该方法的类对应的实例对象被调用时,自动调用该方法
作用
使得一个自定义的实例对象可以像函数那样被调用
案例
class C:
def __call__(self, data): #定义魔法方法__call__
print('__call__方法被调用')
print(data)
ins = C()
ins(10) #通过调用实例对象,自动调用__call__方法
#输出结果:
"""
__call__方法被调用
10
"""
自动被调用的场景
与上下文管理语句(with语句)联合使用,在上下文管理器创建以后,自动调用上下文管理器的该方法
作用
在上下文管理语句中实现资源的访问
案例
class C:
def __init__(self):
self.data = 10
def __enter__(self): #定义魔法方法__enter__
print('__enter__方法被调用')
return self #将实例对象本身返回
def __exit__(self, *args): #定义魔法方法__exit__
print('__exit__方法被调用', *args)
with C() as ins: #通过上下文管理语句,在特定时刻自动调用__enter__和__exit__
print(ins.data)
#输出结果:
"""
__enter__方法被调用
10
__exit__方法被调用 None None None
"""
自动被调用的场景
与上下文管理语句(with语句)联合使用,在上下文关联语句代码块执行完毕后,自动调用上下文管理器的该方法
作用
在上下文管理语句中保证即使发生异常,资源也可以正常关闭,并且可以获取异常的相关信息
值得注意的是如果__exit__配合上下文管理语句使用,必须要设置三个形参(除了self以外的三个形参),用来接收自动传入的三个异常的相关信息
__exit__ 方法中有三个形参,从左往右依次为exc_type(接收异常类型),exc_val(接收异常值),exc_tb(接收异常回溯追踪信息),即这三个形参用来接收处理异常,如果代码在运行时发生异常,异常会被保存到这里
案例
class C:
def __init__(self):
self.data = 10
def __enter__(self): #定义魔法方法__enter__
print('__enter__方法被调用')
return self #将实例对象本身返回
def __exit__(self, *args): #定义魔法方法__exit__
print('__exit__方法被调用', *args)
with C() as ins: #通过上下文管理语句,在特定时刻自动调用__enter__和__exit__
print(ins.data)
#输出结果:
"""
__enter__方法被调用
10
__exit__方法被调用 None None None
"""
自动被调用的场景
在要创建一个实例对象的时候,被自动调用(调用时会自动传入类对象以及在代码"ins = C(指定的实参)"中指定的实参)
作用
创建一个实例对象
值得一提的是__new__方法的返回值就是最后得到的ins,即两者的引用相同
案例
class C:
def __new__(cls): #重写魔法方法__new__
print('__new__方法被调用')
print(cls)
ins = super().__new__(cls) #调用父类的__new__方法创建实例对象
print(ins)
return ins #返回实例对象
ins = C() #通过实例化对象的操作,自动调用__new__方法
print(ins)
#输出结果:
"""
__new__方法被调用
<__main__.C object at 0x0000020B2EA2ADC0>
<__main__.C object at 0x0000020B2EA2ADC0>
"""
class C:
def __new__(cls): #重写魔法方法__new__
print('__new__方法被调用')
return 123
ins = C()
print(ins)
#输出结果:
"""
__new__方法被调用
123
"""
class C:
def __new__(cls, data): #会自动传入类对象和123
print('__new__方法被调用')
print(data)
return super().__new__(cls)
ins = C(123)
#输出结果:
"""
__new__方法被调用
123
"""
自动被调用的场景
_new__方法调用结束后,在_new__方法返回的是本类对象对应的实例对象的时候,会自动调用__init__方法(调用时会自动传入__new__方法返回的实例对象以及在代码"ins = C(指定的实参)"中指定的实参)
作用
对通过__new__方法创建的实例对象进行初始化(比如为该实例对象创建实例属性)
定义一个新的类,并继承一个别人写好的类,如果要重写其__init__方法,那么就需要在重写的__init__方法的开头通过Python代码(如下)来执行父类的__init__方法中的代码,完成对实例对象的基本功能初始化
super().__init__() #执行父类的__init__方法中的代码
案例
class C:
def __new__(cls):
print('__new__方法被调用')
ins = super().__new__(cls)
print(ins)
return ins
def __init__(self): #重写魔法方法__init__
print('__init__方法被调用')
print(self)
ins = C() #通过实例化对象的操作,自动调用__init__方法
print(ins)
#输出结果:
"""
__new__方法被调用
<__main__.C object at 0x00000185ABF3ADC0>
__init__方法被调用
<__main__.C object at 0x00000185ABF3ADC0>
<__main__.C object at 0x00000185ABF3ADC0>
"""
class C:
def __new__(cls):
print('__new__方法被调用')
return 123 #当__new__的返回值不是类对象C对应的实例对象的时候,不会自动调用__init__方法
def __init__(self):
print('__init__方法被调用')
ins = C()
print(ins)
#输出结果:
"""
__new__方法被调用
123
"""
class C:
def __new__(cls, data):
print('__new__方法被调用')
print('__new__方法的', data)
ins = super().__new__(cls)
return ins
def __init__(self, data): #会自动传入__new__返回的实例对象和123
print('__init__方法被调用')
print('__init__方法的', data)
self.data = data #为实例对象创建一个实例属性
ins = C(123)
print(ins.data)
#输出结果:
"""
__new__方法被调用
__new__方法的 123
__init__方法被调用
__init__方法的 123
123
"""
一整个创建实例对象的过程
值得一提的是创建实例对象的过程中,在代码"ins = C(指定的实参)"中指定的实参会自动传入__new__方法与__init__方法中(如果方法被自动调用的话)
自动被调用的场景
对该对象调用iter函数的时候,会被自动调用
作用
在类中定义了__iter__方法,这个类创建出来的对象就是一个可迭代对象,该方法必须返回一个迭代器,具体的案例请移步Python——迭代器
案例
class C:
def __iter__(self): #定义魔法方法__iter__
print('__iter__方法被调用')
return iter([1, 2]) #__iter__必须要返回一个迭代器对象,否则会抛出异常
ins = C()
iter(ins) #通过对该对象调用iter函数自动调用__iter__
#输出结果:__iter__方法被调用
from collections.abc import Iterable
class C1:
def __iter__(self): #定义魔法方法__iter__
pass
class C2:
pass
ins1 = C1()
ins2 = C2()
print(isinstance(ins1, Iterable))
print(isinstance(ins2, Iterable))
#输出结果:
"""
True
False
"""
自动被调用的场景
对该对象调用next函数的时候,会被自动调用
作用
在类中同时定义了__iter__方法和__next__方法,这个类创建出来的对象就是一个迭代器,一般来说,该方法需要返回迭代器中的数据,具体的案例请移步Python——迭代器
案例
class C:
def __next__(self): #定义魔法方法__next__
print('__next__方法被调用')
ins = C()
next(ins) #通过对该对象调用next函数自动调用__next__
#输出结果:__next__方法被调用
from collections.abc import Iterator
class C1:
def __iter__(self):
pass
def __next__(self): #定义魔法方法__next__
pass
class C2:
pass
ins1 = C1()
ins2 = C2()
print(isinstance(ins1, Iterator))
print(isinstance(ins2, Iterator))
#输出结果:
"""
True
False
"""
自动被调用的场景
在某些需要将对象转为字符串的时候被自动调用,比如打印的时候、将在格式化字符串中将对象转化为字符串的时候
作用
不对该方法进行重写,会打印(或者将对象转化为)<模块名.类名 object at 对象id> ,如果进行了重写,会打印(或者将对象转化为)__str__方法的返回值
值得注意的是__str__返回值必须为字符串类型对象,否则会引发异常
案例
class C:
def __init__(self, data):
self.data = data
ins = C(10)
print(ins) #不重写__str__方法就会输出<模块名.类名 object at 对象id>
#输出结果:<__main__.C object at 0x0000024CC5DAA850>
class C:
def __init__(self, data):
self.data = data
def __str__(self): #重写魔法方法__str__
print('__str__方法被调用')
return str(self.data) #__str__必须要返回一个字符串对象,否则会抛出异常
ins = C(10)
print(ins) #通过打印实例对象,自动调用__str__
#输出结果:
"""
__str__方法被调用
10
"""
class C:
def __init__(self, data):
self.data = data
def __str__(self): #重写魔法方法__str__
print('__str__方法被调用')
return str(self.data) #__str__必须要返回一个字符串对象,否则会抛出异常
ins = C(10)
print(f'{ins}') #通过将实例对象转化为字符串对象,自动调用__str__
#输出结果:
"""
__str__方法被调用
10
"""
class C:
def __init__(self, data):
self.data = data
def __str__(self): #重写魔法方法__str__
print('__str__方法被调用')
return str(self.data) #__str__必须要返回一个字符串对象,否则会抛出异常
ins = C(10)
describe_string = str(ins) #通过将实例对象转化为字符串对象,自动调用__str__
print(describe_string)
#输出结果:
"""
__str__方法被调用
10
"""
自动被调用的场景
在使用索引获取数据的时候,会被自动调用
作用
使得对象可以通过下标取出数据
值得注意的是要设置一个形参接收指定的下标值
案例
class C:
def __init__(self):
self.lst = [1, 2, 3, 4]
def __getitem__(self, key): #定义魔法方法__getitem__
print('__getitem__方法被调用')
return self.lst[key]
ins = C()
print(ins[1])
#输出结果:
"""
__getitem__方法被调用
2
"""
class C:
def __init__(self):
self.dct = {'a':1, 'b':2, 'c':3, 'd':4}
def __getitem__(self, key): #定义魔法方法__getitem__
print('__getitem__方法被调用')
return self.dct[key]
ins = C()
print(ins['b'])
#输出结果:
"""
__getitem__方法被调用
2
"""
自动被调用的场景
在使用索引修改(或创建)数据的时候,会被自动调用
作用
使得对象可以通过下标修改(或创建)数据
值得注意的是要设置两个形参分别接收指定的下标值和数据
案例
class C:
def __init__(self):
self.dct = {'a':1, 'b':2, 'c':3, 'd':4}
def __setitem__(self, key, value): #定义魔法方法__setitem__
self.dct[key] = value
ins = C()
print(ins.dct)
ins['b'] = 20
print(ins.dct)
#输出结果:
"""
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
__setitem__方法被调用
{'a': 1, 'b': 20, 'c': 3, 'd': 4}
"""
class C:
def __init__(self):
self.dct = {'a':1, 'b':2, 'c':3, 'd':4}
def __setitem__(self, key, value): #定义魔法方法__setitem__
print('__setitem__方法被调用')
self.dct[key] = value
ins = C()
print(ins.dct)
ins['e'] = 5
print(ins.dct)
#输出结果:
"""
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
__setitem__方法被调用
{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
"""
自动被调用的场景
在使用索引删除数据的时候,会被自动调用
作用
使得对象可以通过下标删除数据
值得注意的是要设置一个形参接收指定的下标值
案例
class C:
def __init__(self):
self.dct = {'a':1, 'b':2, 'c':3, 'd':4}
def __delitem__(self, key): #定义魔法方法__delitem__
print('__delitem__方法被调用')
del self.dct[key]
ins = C()
print(ins.dct)
del ins['a']
print(ins.dct)
#输出结果:
"""
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
__delitem__方法被调用
{'b': 2, 'c': 3, 'd': 4}
"""
自动被调用的场景
在要进行任何属性(无论该属性是否存在,无论是访问实例属性还是类属性)(此处的属性也包括了方法)的访问时,都会自动调用
作用
如果对该方法进行了重写,则返回值作为属性访问得到的结果值,如果对应的类对象中没有定义该方法,则正常返回所访问的属性
通过该方法,可以实现对所要访问的属性进行检测,针对不同的属性,进行不同的操作,从而实现对某一部分特殊属性访问的拦截
在实际应用中,该方法可以实现对某一个属性被访问次数和具体访问时间的记录
值得注意的是要设置一个形参接收所要访问的属性名(一个字符串类型对象)
案例
class C:
def __init__(self):
self.data_1 = 10
self.data_2 = 20
def __getattribute__(self, name): #重写__getattribute__方法
print('__getattribute__方法被调用')
if name == 'data_1': #当要访问的属性名为'data_1',允许访问
return super().__getattribute__(name) #通过调用父类的同名方法,返回数据
else: #当要访问的属性名为'data_2',不允许访问
return '该属性无法被访问'
ins = C()
print(ins.data_1)
print(ins.data_2)
#输出结果:
"""
__getattribute__方法被调用
10
__getattribute__方法被调用
该属性无法被访问
"""
自动被调用的场景
与__getattribute__类似,只不过魔法方法__getattribute__对所有的属性(无论是否存在)都会进行拦截(即__getattribute__为全部拦截),而魔法方法__getattr__只会对一个不存在属性(即实例对象中、类对象中、父类中…都没有该同名属性)进行访问的时候会被自动调用(即__getattr__为部分拦截)
作用
如果对该方法进行了重写,则返回值作为属性访问得到的结果值,如果对应的类对象中没有定义该方法,则抛出异常
在实际应用中,该方法可以实现对所要访问的不存在属性的时间和该属性名进行记录
值得注意的是要设置一个形参接收所要访问的属性名(一个字符串类型对象)而且__getattr__和__getattribute__同时使用的时候,只会触发__getattribute__
案例
class C:
def __init__(self):
self.data_1 = 10
def __getattr__(self, name): #定义__getattr__方法
print('__getattr__方法被调用')
return '该属性不存在'
ins = C()
print(ins.data_1)
print(ins.data_2)
#输出结果:
"""
10
__getattr__方法被调用
该属性不存在
"""
class C:
def __init__(self):
self.data_1 = 10
def __getattribute__(self, name):
print('__getattribute__方法被调用')
if name == 'data_1':
return super().__getattribute__(name)
else:
return '该属性不存在'
def __getattr__(self, name): #定义__getattr__方法
print('__getattr__方法被调用')
return '该属性不存在'
ins = C()
print(ins.data_1)
print(ins.data_2)
#输出结果:
"""
__getattribute__方法被调用
10
__getattribute__方法被调用
该属性不存在
"""
自动被调用的场景
当一个对象被回收(被gc消除回收)的时候,即当对象不再被引用时,Python的垃圾回收机制会自动调用__del__方法
作用
在一个对象被回收的时候,允许我们在对象被销毁之前执行一些清理操作,例如关闭文件、释放内存或释放其他外部资源
这个方法的调用时刻是不好判断的,因为一个对象的引用计数为0的时候会自动被回收,但是使得引用计数增加或者减少的方法有很多
值得注意的是__del__并不是在执行del的时候被自动调用的,即del操作与__del__关联性不大,del操作仅仅就是减少引用计数而已
案例
class C:
def __init__(self):
self.data_1 = 10
def __del__(self): #定义魔法方法__del__
print('__del__方法被调用')
ins = C() #此时实例对象的引用计数为1
a = ins #此时实例对象的引用计数为2
del ins #此时实例对象的引用计数为1
print('del操作结束')
#最后程序运行结束,所有对象都被回收,自动调用__del__
#输出结果:
"""
del操作结束
__del__方法被调用
"""
class C:
def __init__(self):
self.data_1 = 10
def __del__(self):
print('__del__方法被调用')
ins = C() #此时实例对象的引用计数为1
a = ins #此时实例对象的引用计数为2
del ins #此时实例对象的引用计数为1
del a #此时实例对象的引用计数为0,对象被回收,自动调用__del__
print('del操作结束')
#输出结果:
"""
__del__方法被调用
del操作结束
"""