一、描述器 Descriptors
- 描述器的表现:用到3个魔术方法:__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('-'*20) print(B.x.a1) print('='*20) b = B() print(b.x.a1) 可以看出上面代码执行过程:类加载的时候,类变量需要先生成,而类B的x属性是类A的实例,所以类A先初始化,先打印A.init 然后执行到打印B.x.a1,然后实例化并初始化B的实例b,打印b.x.a1,会查找类属性b.x,指向A的实例,所以返回A实例的属性a1的值
1、下面代码加入__get__方法看看执行变化
class A: def __init__(self): self.a1 = 'a1' print('A.init') def __get__(self,instance,owner): print("A.__get__{}{}{}".format(self,instance,owner)) return self #解决返回值None的问题 class B: x = A() def __init__(self): print('B.init') print('-'*20) print(B.x) #print(B.x.a1) #抛出异常AttributeError print('='*20) b = B() print(b.x) # print(b.x.a1) #抛出异常AttributeError 执行输出: A.init -------------------- A.__get__<__main__.A object at 0x00000000049CB518>,None,<class '__main__.B'> None ==================== B.init A.__get__<__main__.A object at 0x00000000049CB518>,<__main__.B object at 0x00000000049CB630>,<class '__main__.B'> None 因为定义了__get__方法,类A就是一个描述器,对类B或者类B的实例的x属性读取称为对类A的实例的访问,就会调用__get__方法 self都是A的实例,owner都是B的类,instance说明 #None表示没有B类的实例,对应调用B.x
二、描述器的定义
- python中,一个类实现了__get__,__set__,__delete__三个中的任何一个方法就是描述器
- 如果仅实现了__get__,就是非数据描述器
- 同时实现了__get__,__set__就是数据描述器
- 如果一个类的类属性设置为描述器,那么它被称为owner属主
1、属性的访问顺序
- 实例的__dict__优先于非数据描述器,数据描述器优先于实例的__dict__
class A: def __init__(self): print(111111111111111111) self.a1 = 'a1' print('A.init') def __get__(self,instance,owner): print(22222222222222222222) print("A.__get__{},{},{}".format(self,instance,owner)) return self #解决返回值None的问题 def __set__(self,instance,value): print(33333333333333333333333) print('A.__set__ {},{},{}'.format(self,instance,value)) self.data = value class B: x = A() def __init__(self): print(4444444444444444444444) print('B.init') self.x = 'b.x' #增加实例属性x # print('-'*20) # print(B.x) # print(B.x.a1) #抛出异常AttributeError print('='*20) b = B() print(b.x) print(b.x.a1) #抛出异常AttributeError 使用set和不使用set看出,原来不是什么数据描述器优先级高,而是把实例的属性从__dict__中去除了, 造成该属性如果是数据描述器优先访问的假象,说到底,属性访问顺序从来没变过
2、python中的描述器
- 描述器在python中应用非常广泛,python的方法都实现为非数据描述器;因此,实例可以重新定义和覆盖方法,这允许单个实例获取与同一个类的其他实例不同的行为
- property()函数实现为一个数据描述器,因此,实例不能覆盖属性的行为
class A: @classmethod def foo(cls): #非数据描述器 pass @staticmethod #非数据描述器 def bar(): pass @property #数据描述器 def z(self): return 5 def getfoo(self): #非数据描述器 return self.foo def __init__(self): #非数据描述器 self.foo = 100 self.bar = 200 #self.z = 300 a = A() print(a.__dict__) foo,bar都可以在实例中覆盖,但是z不可以 #实现StaticMethod装饰器,实现staticmethod装饰器的功能 #类staticmethod装饰器 class StaticMethod: def __init__(self,fn): self._fn = fn def __get__(self,instance,owner): return self._fn class A: @StaticMethod def stmtd(): print('static method') A.stmtd() A().stmtd() #A.clsmtd()的意思就是None(),一定报错,怎么修改 from functools import partial #类classmethod装饰器 class ClassMethod: def __init__(self,fn): self._fn = fn def __get__(self,instance,cls): ret = partial(self._fn,cls) return ret class A: @ClassMethod # ClassMethod(clsmtd) def clsmtd(cls): print(cls.__name__) print(A.__dict__) A.clsmtd A.clsmtd()
3、对实例的数据进行校验
- 思路:写函数,在__init__中先检查,如果不合格,直接抛异常;使用装饰器,使用inspect模块完成
# class Person: # def __init__(self, name:str, age:int): # params = ((name,str),(age,int)) # print(params) # if not self.checkdata(params): # raise TypeEror() # self.name = name # self.age = age # def checkdata(self, params): # for p, t in params: # print(p,t) # if not isinstance(p,t): # return False # return True # p = Person('tom', 20) #描述器方式 ,写入实例属性的时候做检查 class Typed: def __init__(self, name, type): self.name = name self.type = type def __get__(self, instance, owner): if instance is not None: return instance.__dict__[self.name] return self def __set__(self, instance, value): if not isinstance(value, self.type): raise TypeError(value) instance.__dict__[self.name] = value class Person: name = Typed('name', str) age = Typed('age', int) def __init__(self, name:str, age:int): self.name = name self.age = age p = Person('tom',20) #代码看似不错,但是有硬编码,能否直接获取形参类型,使用inspect模块 #测试如下: import inspect params = inspect.signature(Person).parameters print(params) #使用inspect模块完成 import inspect class Typed: def __init__(self, name, age): self.name = name self.type = type def __get__(self, instance, owner): if instance is not None: return instance.__dict__[self.name] return self def __set__(self, instance, value): if not isinstance(value, self.type): raise TypeError(value) instance.__dict__[self.name] = value 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, Typed(name, param.annotation)) return cls @typeassert class Person: def __init__(self, name:str, age:int): self.name = name self.age = age def __repr__(self): return "{} is {}".format(self.name, self.age) #p = Person('tom','20') p = Person('tom', 20) print(p)