目錄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',爲何呢?