七夕节写那些结伴而行的特殊方法
__getattr__和__setattr__
这两个特别简单,__getattr__是通过属性操作符.或者反射getattr(),hasattr()无法获取到指定属性(对象,类,父类)的时候,该方法被调用
__setattr__则是设置属性的时候被调用
class A: def __getattr__(self, item): print('%s找不到这个属性'%item) def __setattr__(self, instance, value): print('设置了%s == %s'%(instance,value)) a = A() a.aa # aa找不到这个属性 hasattr(a,'bb') # bb找不到这个属性 a.aa = 'a' # 设置了aa == a setattr(a,'bb','bb') # 设置了bb == bb
与他相关的一个方法__getattribute__,尝试获取属性时总会调用这个方法(特殊属性或特殊方法除外),当该方法抛出AttributeError时才会调用__getattr__方法.
class A: def __getattr__(self, item): print('%s找不到这个属性'%item) def __setattr__(self, instance, value): print('设置了%s == %s'%(instance,value)) def __getattribute__(self, item): return 1 a = A() print(a.aa) # 1 print(hasattr(a,'bb')) #True a.aa = 'a' # 设置了aa == a setattr(a,'bb','bb') # 设置了bb == bb
__getitem__和__setitem__
对象使用[]时调用的方法, 这里主要说一下切片的原理.
关键字存在一个slice(), 其实这是一个类
print(slice) # <class 'slice'> print(dir(slice)) # ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', # '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', # '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', # '__str__', '__subclasshook__', 'indices', 'start', 'step', 'stop']
其中indices方法可以根据序列长度修改切片信息,返回一个由起始位置,结束位置,步幅组成的元组.
print(slice(0,10,2).indices(6)) # (0, 6, 2) print(slice(-3).indices(6)) # (0, 3, 1)
来看一下我们切片时的操作吧
class A: def __getitem__(self, item): print(item) a = A() a[1:2] # slice(1, 2, None)
当使用切片时item就是slice对象
我们这样就可以定义自己的序列类型的切片操作了
class Start: def __init__(self,lis=None): if lis: if isinstance(lis,list): self.lis = lis else: raise TypeError('类型错误') else: self.lis = [] def push(self,item): self.lis.append(item) def pull(self): return self.lis.pop() def __str__(self): return "%s(%s)"%(self.__class__.__name__,self.lis) def __getitem__(self, item): if isinstance(item,slice): return Start(self.lis[item]) elif isinstance(item,int): return self.lis[item] else: raise TypeError("类型错误") a = Start([1,2,3,4,5]) print(a[2]) # 3 print(a[2:10]) # Start([3, 4, 5])
__get__和__set__
之前搜特殊方法的时候看到这两个是操作描述符时调用的方法,而描述符是什么呢? 实现了__get__, __set__或__delete__方法的类就是一个描述符
描述符的用法是创建一个实例,作为另一个类的类属性.
记得property()吧,特性其实也是描述符. 有一些受保护的属性,他的值需要做一些判断, 这时候我们用了property
class Person: _age = None @property def age(self): return self._age @age.setter def age(self,age): if age>0 and age<100: self._age = age a = Person() a.age=10 print(a.age)
我们通过__get__和__set__也能够做到
class Age: def __init__(self,age=None): self._age = age def __get__(self, instance, owner): print(instance,owner) # <__main__.Person object at 0x0000024396465470> <class '__main__.Person'> return self._age def __set__(self, instance, value): print(instance, value) # <__main__.Person object at 0x000002B109AB2160> 10 if value>0 and value<100: self._age = value else : raise TypeError() class Person: age = Age() a = Person() print(a.age) a.age = 10 b = Person() print(b.age) # 10
干的漂亮b的age也变成了10,为什么? 这就是覆盖型描述符和非覆盖描述符
实现__set__方法的描述符称为覆盖描述符,实现此方法会覆盖对实例属性的赋值操作. 之前我们给实例属性赋值时是在实例的名称空间内
class Person: age = None a = Person() a.age = 10 print(a.age) # 10 print(Person.age) # None
再看一下上个例子
class Age: ... def __str__(self): return self._age print(Person.age) # None <class '__main__.Person'> 10
所以在描述符的例子中我们操作的是描述符实例,也就是类属性的值, 在描述符中是可以操作托管类实例的instance参数就是托管类实例. 通过instance和__dict__我们就可以将值存储在托管类实例的内存空间内了
class Age: def __get__(self, instance, owner): print(instance,owner) # <__main__.Person object at 0x0000024396465470> <class '__main__.Person'> return instance.__dict__.get('age') def __set__(self, instance, value): print(instance, value) # <__main__.Person object at 0x000002B109AB2160> 10 if value>0 and value<100: instance.__dict__['age'] = value else : raise TypeError() class Person: age = Age() a = Person() print(a.age) a.age = 10 b = Person() print(b.age) # 10
自己实现一个property
class Property: def setattr(self, func): self.set = func @staticmethod def _set(*args): raise AttributeError("can't set attribute") def __init__(self,get,set=None,delter=None): self.get = get if set: self.set = set else: self.set = Property._set self.delter = delter def __get__(self, instance, owner): return self.get(instance) def __set__(self, instance, value): self.set(instance,value) class Person: _age = 0 @Property def age(self): return self._age @age.setattr def getage(self,age): if age > 0 and age < 100: self._age = age # def getage(self): # print('获取值') # return self._age # age = Property(getage) a = Person() print(a.age) a.age = 10 print(a.age) b = Person() print(b.age)
描述符的分类:
- 覆盖性描述符实现了__set__方法的描述符. 该方法会覆盖对实例属性的赋值操作, 用法建议例如只读属性可以在__set__中抛出异常
- 没有实现__get__方法的描述符, 获取实例属性时会得到描述符实例, 用于验证可以只使用__set__方法
- 没有实现__set__方法的描述符是非覆盖性描述符. 设置同名的实例属性描述符会被遮盖.
值的注意的是,无论是不是覆盖性描述符,为类属性赋值都能覆盖描述符.
事实上方法就是描述符,我们可以查看函数的方法
class bb: def aa(self): pass # function b = bb() print(b.aa) # <bound method bb.aa of <__main__.bb object at 0x000001EBE99853C8>> print(bb.aa) # <function bb.aa at 0x000001EBF89C8B70> print(dir(bb.aa)) # ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', # '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', # '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', # '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', # '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', # '__str__', '__subclasshook__']
我们可以看到函数实现了__get__方法, 通过instance来判断是否是由对象来调用的, 通过托管类访问时返回的是自身. 通过托管实例访问时会将instance绑定给函数的第一个参数, 类似partial
from functools import partial class Func: def __get__(self, instance, owner): if not instance: return self else: return partial(self, instance) def __call__(self, *args, **kwargs): print(11111) class A: func = Func() print(A.func) a = A() print(a.func)
__enter__ 和 __exit__
上下文协议的两个方法, with后面的语句被求值后,返回对象的 __enter__() 方法被调用,这个方法的返回值将被赋值给as后面的变量。
也就是说with语句后表达式的结果必须是实现了上下文协议的类的对象. 并且__enter__方法的返回值会被赋给as后的变量
class A: def __enter__(self): print('进来了') return self def __exit__(self, exc_type, exc_val, exc_tb): print('出去了') def b(self): print('执行了') with A() as a: a.b()
在Flask中的AppContext类中可以看到类似的用法
__exit__方法中有三个参数, 当一切正常时, 这三个参数都是None, 有异常抛出时三个参数分别是异常类, 异常实例, 以及traceback对象.
class A: def __enter__(self): print('进来了') return self def __exit__(self, exc_type, exc_val, exc_tb): print(1,exc_type) print(2,exc_val) print(3,exc_tb) print('出去了') def b(self): print('执行了') with A() as a: a.b() raise TypeError('我错啦') # 执行如下 进来了 Traceback (most recent call last): 执行了 File "D:/python练习/yian2/shihui/tests.py", line 181, in <module> 1 <class 'TypeError'> raise TypeError('我错啦') 2 我错啦 TypeError: 我错啦 3 <traceback object at 0x0000022B387DFCC8> 出去了
这里也可以控制跳过某一个异常,只需要__exit__方法返回True即可, 修改__exit__方法如下
def __exit__(self, exc_type, exc_val, exc_tb): print(1,exc_type) print(2,exc_val) print(3,exc_tb) print('出去了') if isinstance(exc_val,TypeError): return True # 进来了 执行了 1 <class 'TypeError'> 2 我错啦 3 <traceback object at 0x000001A0A715FCC8> 出去了
如果with语句块中跑出了多个异常??修改with语句如下
with A() as a: a.b() raise TypeError('我错啦') print('我错没错') raise TypeError('我没有错') # 进来了 执行了 1 <class 'TypeError'> 2 我错啦 3 <traceback object at 0x000002A215D7FCC8> 出去了
可见with语句遇到一个没有被捕获的异常时便会退出