目录python
通常来讲,一个描述器是一个有绑定行为
的对象属性(object attribute),它的访问控制被描述器协议方法重写。这些方法是 __get__(), __set__(), 和 __delete__() 。app
有这些方法的对象叫作描述器。函数
默认对属性的访问控制是从对象的字典里面(__dict__)中获取、设置和删除它。举例来讲, 好比 a.x 的查找顺序是, a.__dict__['x'] , 而后 type(a).__dict__['x'] , 而后找 type(a) 的父类(不包括元类(metaclass)). 若是查找到的值是一个描述器, Python就会调用描述器的方法来重写默认的控制行为。这个重写发生在这个查找环节的哪里取决于定义了哪一个描述器方法。注意, 只有在新式类中时描述器才会起做用。(新式类是继承自 type 或者 object 的类)。编码
描述器主要涉及三个方法:code
一个对象具备其中任一个方法就会成为描述器,从而在被看成对象属性时重写默认的查找、设置和删除行为。orm
在类中仅仅定义了__get__方法的描述器被称为非数据描述器(non-data descriptor)。对象
非数据描述器的优先级低于实例的__dict__。继承
class A: def __init__(self): self.a1 = 'a1' print('A.init') def __get__(self, instance, owner): pass # return self class B: x = A() def __init__(self): print('B.init') print('-' * 20) b = B() print(b.x.a1) # Traceback (most recent call last): # A.init # -------------------- # B.init # File "E:/Python - base - code/ClassDesc.py", line 20, in <module> # print(b.x.a1) # AttributeError: 'NoneType' object has no attribute 'a1'
分析:递归
那么self是什么,__get__方法的参数都是什么意思:ip
self
:对应A的实例(这里是属性x)owner
:对应的是x属性的拥有者,也就是B类instance
:它的值有两个
它是None
实例自己
下面是小例子,分析代码结果
class Person: def __init__(self): self.country = 'Earth' def __get__(self, instance, owner): return self.country class ChinaPeople: country = Person() def __init__(self,name,country): self.name = name self.country = country daxin = ChinaPeople('daxin','China') print(daxin.country)
分析:
同时实现了__get__、__set__方法就称为数据描述器(data descriptor)
数据描述器的优先级高于实例的字典__dict__。
class A: def __init__(self): self.name = 'A' def __get__(self, instance, owner): print('From A __get__') return self.name def __set__(self, instance, value): print('From A __set__') class B: name = A() def __init__(self): self.name = 'B' b = B() print(b.name) # 结果: # From A __set__ # From A __get__ # A
分析:
那么self是什么,__set__方法的参数都是什么意思:
self
:对应A的实例(这里是属性name)instance
:对应的是实例自己,这里就是bvalue
:表示设置的值(这里就是'B')分析下面代码的运行原理
class A: def __init__(self): self.name = 'A' def __get__(self, instance, owner): print('From A __get__') # return self.name return instance.__dict__['name'] def __set__(self, instance, value): print('From A __set__') instance.__dict__['name'] = value class B: name = A() def __init__(self): self.name = 'B' b = B() print(b.name)
分析:
当类中存在描述器时,那么对象属性的调用就会发生变化。根据上面的例子,咱们知道,实例属性访问的优先级为:数据描述器 > 实例字典__dict__ > 非数据描述器
特别注意:这里的访问顺序指的是:
实例属性对应一个描述器时的顺序。
,若是直接对类属性进行赋值操做,会直接覆盖类的描述器。
结合前面学的魔术方法,分析整个过程。
使用Pyhon描述这个过程就是
def __getattribute__(self, key): print('from B __getattribute__') v = super(B, self).__getattribute__(key) # 这里用 self.__getattribute__就会递归了 # v = object.__getattribute__(self, key) # 使用super的方法,等同于直接调用object if hasattr(v, '__get__'): return v.__get__(self, type(self)) return v
完整的代码:
class A: def __init__(self): self.name = 'A' def __get__(self, instance, owner): print('From A __get__') # return self.name return instance.__dict__['name'] def __set__(self, instance, value): print('From A __set__') instance.__dict__['name'] = value class B: name = A() def __init__(self): self.name = 'B' def __getattribute__(self, key): print('from B __getattribute__') v = super(B, self).__getattribute__(key) if hasattr(v, '__get__'): return v.__get__(self, type(self)) return v b = B() print(b.name)
总结几点比较重要的:
描述器在Python中应用很是普遍。咱们定义的实例方法,包括类方法(classmethod)和静态方法(staticmethod)都属于非数据描述器。因此实例能够从新定义和覆盖方法。这样就可使一个实例拥有与其余实例不一样的行为(方法重写)。
但property装饰器否则,它是一个数据描述器,因此实例不能覆盖属性。
class A: def __init__(self,name ): self._name = name @staticmethod def hello(): # 非数据描述器 print('world') @classmethod def world(cls): # 非数据描述器 print('world') @property def name(self): # 数据描述器 return self._name def welcome(self): # 非数据描述器 print('Welcome') class B(A): def __init__(self,name): super().__init__(name) daxin = B('daxin') daxin.hello = lambda : print('modify hello') # 能够被覆盖 daxin.world = lambda : print('modify world') # 能够被覆盖 daxin.welcome = lambda : print('modify welcome') # 能够被覆盖 daxin.name = lambda self: self._name # 没法被覆盖 daxin.hello() daxin.world() daxin.welcome()
下面是一个简单的StaticMethod的实现
class StaticMethod: def __init__(self, fn): self.fn = fn def __get__(self, instance, owner): return self.fn class A: @StaticMethod # hello = StaticMethod(hello) def hello(): print('hello world') daxin = A() daxin.hello() # hello() = StaticMethod().fn()
静态方法不须要传参,那么只须要在__get__方法拦截后,仅仅返回方法自己便可。
import functools class ClassMethod: def __init__(self, fn): self.fn = fn def __get__(self, instance, owner): #return lambda : self.fn(owner) return functools.partial(self.fn,owner) class A: @ClassMethod def hello(cls): print('hello world {}'.format(cls.__name__)) daxin = A() daxin.hello() # hello() = functools.partial(self.fn,owner)
类方法因为默认会把类看成参数传递,因此须要把方法的第一个参数固定为类,因此使用偏函数来固定,是一个比较好的办法,又或者使用lambda,因为lambda函数只能接受一个参数,因此当类方法是多个参数时,没法接受。
现有以下代码:
class Person: def __init__(self,name:str, age:int): self.name = name self.age = age
对上面类的属性name,age进行数据类型的校验。
思路:
通常人都会
)多数人会
)少数人会
)基本没人会
)class Person: def __init__(self, name:str, age:int): # 每次都判断,而后赋值 # if self._typecheck(name,str): # self.name = name # if self._typecheck(age, int): # self.age = age # 或者直接构建须要的数据类型,一次性判断,最后赋值 params = [(name,str),(age,int)] for param in params: if not self._typecheck(*param): raise TypeError(param[0]) self.name = name self.age = age def _typecheck(self,value,typ): if not isinstance(value, typ): raise TypeError(value) return True daxin = Person('daxin',20) print(daxin.name) print(daxin.age)
看起来也太丑了,不能复用不说,在初始化阶段还作了大量的逻辑判断,也不容易让别人明白你真正的意图是啥。
import inspect def TypeCheck(cls:object): def wrapper(*args,**kwargs): sig = inspect.signature(cls) # 获取签名对象 param = sig.parameters.values() # 抽取签名信息(有序) data = zip(args,param) # 构建值与类型的元组 for value,typ in data: if typ.annotation != inspect._empty: # 当定义了参数注解时,开始参数判断 if not isinstance(value,typ.annotation): raise TypeError(value) # 判断不经过,爆出异常 return cls(*args,**kwargs) return wrapper @TypeCheck # Person = TypeCheck(Person)('daxin',20) ==> wrapper('daxin',20) class Person: def __init__(self,name:str, age:int): self.name = name self.age = age daxin = Person('daxin','20') print(daxin.name) print(daxin.age)
看起来很好的解决了参数类型的检查,而且也能够针对不一样类继续进行参数检查,因此说:装饰器
,真香
。
class TypeCheck: def __init__(self, name, typ): self.name = name self.typ = typ def __get__(self, instance, owner): return instance.__dict__[self.name] def __set__(self, instance, value): if not isinstance(value,self.typ): raise TypeError(value) instance.__dict__[self.name] = value class Person: name = TypeCheck('name',str) # 硬编码 age = TypeCheck('age',int) # 硬编码 def __init__(self, name:str, age:int): self.name = name self.age = age daxin = Person('daxin','20') print(daxin.name) print(daxin.age)
import inspect class TypeCheck: def __init__(self, name, typ): self.name = name self.typ = typ def __get__(self, instance, owner): return instance.__dict__[self.name] def __set__(self, instance, value): if not isinstance(value,self.typ): raise TypeError(value) instance.__dict__[self.name] = value # 动态注入name,age描述器属性 def AttriCheck(cls:object): def wrapper(*args,**kwargs): sig = inspect.signature(cls) params = sig.parameters for k,v in params.items(): print(v.annotation) if v.annotation != inspect._empty: if not hasattr(cls,k): setattr(cls,k,TypeCheck(k,v.annotation)) return cls(*args,**kwargs) return wrapper @AttriCheck # Person = AttriCheck(Person) class Person: def __init__(self, name: str, age: int): self.name = name self.age = age a = Person('daxin', 20) print(a.name) print(a.age)
使用装饰器结合描述器时,类必须包含对应同名描述器,才能够利用描述器进行参数检查,因此,利用反射,将参数注入类中,而后经过描述器进行检查
可否把上面的装饰器函数,改成类?
import inspect class TypeCheck: def __init__(self, name, typ): self.name = name self.typ = typ def __get__(self, instance, owner): return instance.__dict__[self.name] def __set__(self, instance, value): if not isinstance(value,self.typ): raise TypeError(value) instance.__dict__[self.name] = value class AttriCheck: def __init__(self,cls): self.cls = cls def __call__(self, *args, **kwargs): sig = inspect.signature(self.cls) params = sig.parameters for name,typ in params.items(): if typ.annotation != inspect._empty: if not hasattr(self.cls, name): setattr(self.cls,name,TypeCheck(name,typ.annotation)) return self.cls(*args,**kwargs) @AttriCheck # Person = AttriCheck(Person) class Person: def __init__(self, name: str, age: int): self.name = name self.age = age a = Person('daxin', '20') print(a.name) print(a.age)
看下面例子:
class B: def __init__(self, data): self.data = data def __get__(self, instance, owner): return self.data def __set__(self, instance, value): self.data = value class C: name = B('daxin') age = B(20) def __init__(self, name, age): self.name = name self.age = age daxin = C('tom',18) dachenzi = C('Jack',29) print(daxin.name)
结果是'Jack',为何呢?