21、描述器:Descriptors
1)描述器的表现
用到三个魔术方法。__get__() __set__() __delete__()
方法签名如下:
object.__get__(self,instance,owner)
object.__set__(self,instance,value)
object.__delete__(self,instance)
Self指指代当前实例,调用者。
Instance是owner的实例。
Owner是属性所属的类。
class A:
def __init__(self):
self.a1 = 'a1'
print('A init')
class B:
x = A()
def __init__(self):
print('B init')
print(B.x.a1) #A init a1
b = B()
print(b.x.a1) # B init a1
执行顺序是:第一个print执行类A的 第二个print执行的是类B。
class A:
def __init__(self):
self.a1 = 'a1'
print('A init')
def __get__(self, instance, owner):
print('{}{}{}'.format(self,instance,owner))
class B:
x = A()
def __init__(self):
print('B init')
# print(B.x.a1) # 会抛出错误,属性异常
#
# b = B()
# print(b.x.a1) #会抛出错误,属性异常
出现错误的原因是:类A中定义get方法,那么类A就是描述器,报错的原因是和类A中的get方法的返回有关系。
get方法return返回值后:
class A:
def __init__(self):
self.a1 = 'a1'
print('A init')
def __get__(self, instance, owner):
print('{}{}{}'.format(self,instance,owner))
return self
class B:
x = A()
def __init__(self):
print('B init')
print(B.x.a1)
b = B()
print(b.x.a1)
A init
<__main__.A object at 0x00000090E02F2F60>None<class '__main__.B'>
a1
B init
<__main__.A object at 0x00000090E02F2F60><__main__.B object at 0x00000090E02F9518><class '__main__.B'>
a1
查看是否除了类属性以外,实例的属性是否可以触发get方法呢?
class A:
def __init__(self):
self.a1 = 'a1'
print('A init')
def __get__(self, instance, owner):
print('{}{}{}'.format(self,instance,owner))
return self
class B:
x = A()
def __init__(self):
print('B init')
self.b = A()
print(B.x.a1)
b = B()
print(b.x.a1)
print(b.b)
总结:所以只有类属性是类的实例才可以。
2)描述器定义
描述器是一个类的类属性是另一个类的实例,另一个类中实现了set、delete和get方法之一。
有两个类A,B,类A中实现set和get的属性方法。类B中的一个属性为类A。
用一个类来增强另一个类的功能。
练习:模仿property描述器
class Property:
def __init__(self,fget,fset = None):
self.fget = fget
self.fset = fset
def __get__(self, instance, owner):
if instance is not None:
return self.fget(instance)
return self
def __set__(self, instance, value):
print(self,instance,value)
if callable(self.fset):
self.fset(instance,value)
else:
raise AttributeError
def setter(self,fn):
self.fset = fn
return self
class A:
def __init__(self,data):
self._data = data
@Property #data = Property(data) #实例化
def data(self):
return self._data
@data.setter #data是Property的实例了 project.setter(data)
def data(self,value): #通过data.setter,提取参数,给fset. data等价于 = self
self._data = value #data = Property(data)
a = A(100)
print(a.data) #data>>值 <<函数(self)
a.data = 200
描述器定义:
描述器必须是类属性,Python中,一个类实现了__get__ 、__set__、__delete__三个任意一个方法都称为描述器。
仅仅实现__get__ 非数据描述器。non-data descriptor
实现__get__ 和__set__ 就是数据描述器。data descriptor
如果一个类的类属性设置为描述器,那么他被称为owner属主。
3)属性的访问顺序
非数据描述器,首先查找的是自己本身的字典。
数据描述器,首先查找的是类的字典。
class A:
def __init__(self):
self.a1 = 'a1'
print('A init')
def __get__(self, instance, owner):
print('{}{}{}'.format(self,instance,owner))
return self
class B:
x = A()
def __init__(self):
print('B init')
self.x = 'b.x'
print(B.x.a1)
b = B()
print(b.x.a1) #会抛出错误,属性异常
b.x访问到了实例的属性,而不是描述器。
修改代码,为类A增加__set__方法。
class A:
def __init__(self):
self.a1 = 'a1'
print('A init')
def __get__(self, instance, owner):
print('{}{}{}'.format(self,instance,owner))
return self
def __set__(self, instance, value):
print('{}{}{}'.format(self,instance,value))
self.data = value
class B:
x = A()
def __init__(self):
print('B init')
self.x = 'b.x'
print(B.x.a1)
b = B()
print(b.x)
print(b.x.a1)
A init
<__main__.A object at 0x0000006AD6EC9588>None<class '__main__.B'>
a1
B init
<__main__.A object at 0x0000006AD6EC9588><__main__.B object at 0x0000006AD6EC95F8>b.x
<__main__.A object at 0x0000006AD6EC9588><__main__.B object at 0x0000006AD6EC95F8><class '__main__.B'>
<__main__.A object at 0x0000006AD6EC9588>
<__main__.A object at 0x0000006AD6EC9588><__main__.B object at 0x0000006AD6EC95F8><class '__main__.B'>
a1
返回变成了a1,访问到了描述器的数据。
属性查找顺序:
实例的 __dict__ 优先于非数据描述器。
数据描述器优先于实例的 __dict__
4)本质(进阶)
不是因为数据描述器优先级高,而是把实例的属性从__dict__ 中去除掉了,数据访问的顺序还是按照原来的顺序执行。
5)Python中的描述器应用
描述器在Python中应用广泛,Python的方法(包括staticmethod()和classmethod())都实现为非数据描述器,因此,实例重新定义和覆盖方法,允许单个实例获取与同一类的其他实例不同的行为。
****实现装饰器staticmethod和classmethod。
from functools import partial
class StaticMethod:
def __init__(self,fn):
self.fn = fn
def __get__(self, instance, owner):
return self.fn
class ClassMethod:
def __init__(self,fn):
self.fn = fn
def __get__(self, instance, cls):
return partial(self.fn,cls)
class A:
@StaticMethod #s_meth = StaticMethod(s_meth)
def s_meth():
print('static method')
@ClassMethod
def c_meth(cls):
print('{}class method'.format(cls))
A.s_meth()
A.c_meth()
6)对实例的数据进行校验。
class Person:
def __init__(self,name:str,age:int):
params = ((name,str),(age,int))#利用二元组判断
if not self.checkdata(params): #如果不为真,就抛出异常
raise TypeError
self.name = name
self.age = age
def checkdata(self,params):
for p,t in params:
if not isinstance(p,t):
return False
return True
(1)描述器方式:
class Check:
def __init__(self,name,type):
self.name = name
self.type = type
def __set__(self, instance, value):
if not isinstance(value,self.type):#如果值和要求的类型不一致,则抛出异常,如果是则按照字典对应key和value值。
raise TypeError(value)
instance.__dict__[self.name] = value
def __get__(self, instance, owner):
if instance is not None:#如果对象不为空,返回的实例对象的字典名称对应的值,如果为空,则是返回实例本身。
return instance.__dict__[self.name]
return self
class Person:
name = Check('name',str)
age = Check('age',int)
def __init__(self,name:str,age:int):
self.name = name
self.age = age
Person('tom',20)
Person('tom','20')
(2)利用装饰器
class Check:
def __init__(self,name,type):
self.name = name
self.type = type
def __set__(self, instance, value):
if not isinstance(value,self.type):
raise TypeError(value)
instance.__dict__[self.name] = value
def __get__(self, instance, owner):
if instance is not None:
return instance.__dict__[self.name]
return self
import inspect
def typeassert(cls):
params = inspect.signature(cls).parameters #获取签名
print(params)
for name,param in params.items():
print(param.name,param.annotation)
if param.annotation != param.empty:#设置的属性不为空,检查是否和设置的属性一致。
setattr(cls,name,Check(name,param.annotation))
return cls
@typeassert
class Person:
# name = Check('name',str)
# age = Check('age',int)
def __init__(self,name:str,age:int):
self.name = name
self.age = age
def __repr__(self):
return '{}is{}'.format(self.name,self.age)
p1 = Person('tom',20)
print(p1)
(3)封装成为类
class Check:
def __init__(self,name,type):
self.name = name
self.type = type
def __set__(self, instance, value):
if not isinstance(value,self.type):
raise TypeError(value)
instance.__dict__[self.name] = value
def __get__(self, instance, owner):
if instance is not None:
return instance.__dict__[self.name]
return self
import inspect
class typeassert #定义为一个类
# def typeassert(cls):
def __init__(self,cls):
self.cls = cls
params = inspect.signature(self.cls).parameters
print(params)
for name,param in params.items():
print(param.name,param.annotation)
if param.annotation != param.empty:
setattr(cls,name,Check(name,param.annotation))
print(self.cls.__dict__)
def __call__(self, name, age): #构建可调用对象。
p = self.cls(name,age) #重新构建一个新的Person对象。
return p
@typeassert
class Person:
# name = Check('name',str)
# age = Check('age',int)
def __init__(self,name:str,age:int):
self.name = name
self.age = age
def __repr__(self):
return '{}is{}'.format(self.name,self.age)
p1 = Person('tom',20)
print(p1)
7)练习题,模仿property装饰器。
class Property:
def __init__(self,fget,fset):
self.fget = fset
self.fset = fset
def __get__(self, instance, owner):
if instance in not None:
return self.fget(instance)
return self
def __set__(self, instance, value):
self.fset(instance,value)
def setter(self,fn):
self.fset = fn
return self
class A:
def __init__(self,data):
self._data = data
@Property #data = Property(data)
def data(self):
return self._data
@data.setter
def data(self,value):
self._data = value