魔术方法
在Python中,被双下滑线包围起来的方法(定义在类中)叫魔术方法(Magic Method),比如我们最常见的__init__()
就是一个典型的魔术方法
魔术方法通常都是满足一定的条件时自动调用,魔术方法可以实现一些特殊的功能
Python语言参考手册中的“Data Model”(https://docs.python.org/3/reference/datamodel.html)一章列出了83个魔术方法
1 构造、初始化和析构
__new__() # 总是在初始化方法被执行前执行——实际上是在当类的实例被创建的同时执行(而初始化方法则是之后的故事了),有返回值且返回该类的一个实例
__init__() # 初始化方法,最常用的,使用时注意:1)写在类里面的方法第一个参数self不要漏写,这一点对所有方法同样适用,2)__new__()是基于__new__()方法的,不需要返回值
__del__() # 相当于Java的析构函数,在对象要被销毁前执行
class Student:
def __new__(cls, *args, **kwargs):
return super().__new__(cls)
def __init__(self, filename):
self.file = open(filename, 'r+')
def __del__(self):
self.file.close()
s = Student('data.txt')
print(s)
2 属性访问相关魔术方法
__getattr__(self, item) # 获取不存在的属性时,触发该方法。指定返回值,得到的属性值就是该返回值
__getattribute__(self, item) # 获取属性时,不论属性是否存在,调用该方法,并发属性名以字符串形式传入,指定该方法的返回值就是 获取的属性的值 。 该方法与__getattr__() 同时存在,该方法优先级更高。 触发该方法内异常
__setattr__(self, item, value) # 设置属性的值时触发
__delattr__() # 删除属性的值时触发
class Student:
name = '丹丹'
age = 10
def __getattribute__(self, item):
return super().__getattribute__(item)
def __getattr__(self, item):
return '不存在的属性'
def __setattr__(self, key, value):
# super().__setattr__(key, value)
self.__dict__[key] = value
def __delattr__(self, item):
# super().__delattr__(item)
self.__dict__.pop(item)
s = Student()
print(s)
s.name = '莉莉'
s.name = 'lili'
del s.name
print(s.name)
print(s.address)
3 对象具有列表字典特性
__setitem__(self, item, value) # obj['key'] = value 时自动触发
__getitem__(self, item) # 取值 obj['key'] 时自动触发
__delitem__(self, item) # del obj['key'] 删除key时自动触发
class Student:
def __getitem__(self, item):
if item in self.__dict__:
return self.__dict__[item]
else:
return None
def __setitem__(self, key, value):
self.__dict__[key] = value
def __delitem__(self, key):
self.__dict__.pop(key)
s = Student()
s['name'] = '丹丹'
print(s['name'])
del s['name']
print(s['name'])
# 斐波那锲数列
class Fib:
def __getitem__(self, item):
a, b = 1, 1
for x in range(item):
a, b = b, a + b
return a
f = Fib()
print(f[1])
print(f[4])
# 升级版 斐波那锲数列
class Fib(object):
def __getitem__(self, n):
if isinstance(n, int): # n是索引
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
if isinstance(n, slice): # n是切片
start = n.start
stop = n.stop
if start is None:
start = 0
a, b = 1, 1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a, b = b, a + b
return L
f = Fib()
print(f[4])
print(f[4:10])
4 对象 字符串形式输出
__str__() # str(obj) 或者 print(obj)输出, 触发,要求返回字符串
__repr__() # repr(obj)或者交互式解释器 触发,要求返回字符串
class School:
def __init__(self,name,addr,type):
self.name=name
self.addr=addr
self.type=type
def __repr__(self):
return 'School(%s,%s)' %(self.name,self.addr)
def __str__(self):
return '(%s,%s)' %(self.name,self.addr)
5 对象变为迭代器
__iter__() # 使对象可以被 for...in 迭代,返回一个迭代器对象
__next__() # 返回下一次迭代的值, 两个通常成对出现
# 简单的迭代器 无限自然数
class Foo:
def __init__(self,x):
self.x=x
def __iter__(self):
return self
def __next__(self):
n=self.x
self.x+=1
return self.x
f=Foo(3)
for i in f:
print(i)
# 模拟range功能
class Range:
def __init__(self,start,stop,step):
self.start = start
self.stop = stop
self.step = step
def __next__(self):
if self.start >= self.stop:
raise StopIteration
x=self.start
self.start += self.step
return x
def __iter__(self):
return self
for i in Range(1,7,3):
print(i)
# 朕最喜欢的 斐波那锲数列
class Fib:
def __init__(self):
self._a=1
self._b=1
def __iter__(self):
return self
def __next__(self):
self._a,self._b=self._b,self._a + self._b
return self._a
for i in f1:
if i > 100:
break
print('%s ' %i,end='')
5 把对象当函数调用
__call__() # 把对象当函数调用时自动触发
class Foo:
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo()
obj() # 执行 __call__
6 把with
语句用于对象
__enter__() # 出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量
__exit__() # with中代码块执行完毕时执行
上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__
和__exit__
方法
class Open:
def __init__(self,name):
self.name=name
def __enter__(self):
print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('with中代码块执行完毕时执行我啊')
with Open('a.txt') as f:
print(f,f.name)
__exit__()
中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行
class Open:
def __init__(self,name):
self.name=name
def __enter__(self):
print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')
def __exit__(self, exc_type, exc_val, exc_tb):
print('with中代码块执行完毕时执行我啊')
print(exc_type)
print(exc_val)
print(exc_tb)
with Open('a.txt') as f:
print('=====>执行代码块')
raise AttributeError('***着火啦,救火啊***')
print('0'*100) #------------------------------->不会执行
如果__exit__()
返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行
class Open:
def __init__(self,name):
self.name=name
def __enter__(self):
print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')
def __exit__(self, exc_type, exc_val, exc_tb):
print('with中代码块执行完毕时执行我啊')
print(exc_type)
print(exc_val)
print(exc_tb)
return True
with Open('a.txt') as f:
print('=====>执行代码块')
raise AttributeError('***着火啦,救火啊***')
print('0'*100) #------------------------------->会执行
作用:
1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预
2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在
__exit__
中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处
7 __slots__
__slots__
是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)
__dict__
属性:使用点来访问属性本质就是在访问类或者对象的
__dict__
属性字典(类的字典是共享的,而每个实例的是独立的)__slots__
的作用 :__dict__
会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__
取代实例的__dict__
当你定义__slots__
后,__slots__
就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,这跟元组或列表很类似。在__slots__
中列出的属性名在内部被映射到这个数组的指定小标上。使用
__slots__
一个不好的地方就是我们不能再给实例添加新的属性了,只能使用在__slots__
中定义的那些属性名。基于这个特性,它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__
可以达到这样的目的,但是这个并不是它的初衷。更多的是用来作为一个内存优化工具。注意事项:
__slots__
的很多特性都依赖于普通的基于__dict__
的实现。另外,定义了__slots__
后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该只在那些经常被使用到 的用作数据结构的类上义__slots__
比如在程序中需要创建某个类的几百万个实例对象 。
class Foo:
__slots__='x'
f1=Foo()
f1.x=1
f1.y=2#报错
print(f1.__slots__) #f1不再有__dict__
class Bar:
__slots__=['x','y']
n=Bar()
n.x,n.y=1,2
n.z=3#报错