描述器
描述器的表现
用到三个魔术方法,__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('~~~~~~~~~') b = B() print(b.x.a1)
#运行结果
A.init
____________________
a1
~~~~~~~~~
B.init
a1
可以看出执行的先后顺序,类加载的时候,类变量需要先生成,而类B的属性是类A的实例,所以A先初始化,打印A.init,
再执行调类的属性也就是A的实例的属性,打印a1
实例化B并初始化,打印B.init
打印b.x.a1,会查找类属性b.x,指向A的实例,所以返回A实例的属性a1的值
接着对类A中实现__get__方法,
class A: def __init__(self): self.a1 = 'a1' print('A.init') def __get__(self, instance, owner): print('A.__get',self,instance,owner) return self class B: x = A() def __init__(self): print('B.init') print('_'*20) print(B.x.a1) print('~~~~~~~~~') b = B() print(b.x.a1)
因为定义了__get__方法,类A就是一个描述器,对类B或者类B的实例的属性获取,成为对类A的实例的访问,就会调用__get__方法,get方法都是去取值的,返回值就是A的实例,实例有a1 的属性,返回正常。
测试一下,B的实例并不能触发__get__
描述器的定义
Python中,一个类实现了__get__,__set__,__delete__三个方法中任何一个方法,就是描述器
如果仅实现了__get__,就是非数据描述器 non-data descriptor;
如果同时实现了__get__,__set__就是数据描述符。
如果一个类的类属性设置为描述器实例,那么它被称为owner属主
属性的访问顺序
实例的__dict__优先于非数据描述器
数据描述器优先于实例的__dict__
__delete__方法有同样的效果,有了这个方法,也是数据描述器
b.x = 500 对象可用,调用了数据描述器的__set__方法,或调用非数据描述器的实例覆盖
B.x = 600 赋值即定义,这是覆盖类属性。
python中的描述器
描述器在Python中应用十分广泛,
python的staticmethod和classmethod都是实现为非数据描述器
因此,实例可以重新定义和覆盖方法,这允许单个实例获取与同一类其他实例的不同的行为
staticmethod的简易实现:
class StaticMethod: def __init__(self,fn): self.foo = fn def __get__(self, instance, owner): return self.foo class B: @StaticMethod def foo(): print('static method') B.foo() B().foo()
classmethod简易实现
import functools class ClassMethod: def __init__(self,fn): self.foo = fn def __get__(self, instance, owner): return functools.partial(self.foo,owner)###如果直接返回self.foo(owner),就是返回这个函数的执行结果,也就是返回None,None()无法执行 class G: @ClassMethod###foo(cls) = ClassMethod(foo)(cls) def foo(cls): print(cls.__name__) G.foo()
描述器实现数据检查,使用inspect模块。
import inspect class LoggerCheck: 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 instance.__dict__[self.name] = value def typeassert(cls): params = inspect.signature(cls).parameters for name,type in params.items(): if type.annotation != inspect._empty: setattr(cls,name,LoggerCheck(name,type.annotation))##在类中注入属性,指向数据检查的实例LoggerCheck return cls @typeassert ##Person = typeasser(Person) class Person: def __init__(self,name:str,age:int): self.name = name self.age = age tom = Person('tom',15)
以上的装饰器可以改为类装饰器
import inspect class LoggerCheck: 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 instance.__dict__[self.name] = value class TypeAssert: def __init__(self,cls): params = inspect.signature(cls).parameters for name, type in params.items(): if type.annotation != inspect._empty: setattr(cls, name, LoggerCheck(name, type.annotation)) self.cls = cls def __call__(self, name,age): return self.cls(name,age) @TypeAssert ##Person = TypeAsser(Person) class Person: def __init__(self,name:str,age:int): self.name = name self.age = age tom = Person('tom',15)