__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语句遇到一个没有被捕获的异常时便会退出