描述器用到的方法
用到3個魔術方法: __get__()、__set__()、__delete__() 方法使用格式: obj.__get__(self, instance, owner) obj.__set__(self, instance, value) obj.__delete__(self, instance) self: 指當前類的實例本身 instance: 指owner的實例 owner: 指當前實例作爲屬性的所屬類
代碼一
以下代碼執行過程: 定義B類時,執行A()賦值操作,進行A類的初始化,再打印B類調用類屬性x的a1屬性 緊接着執行B類的初始化,通過b實例調用類屬性的x的a1屬性 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)
描述器定義
Python中,一個類中實現了__get__、__set__、__delete__三個方法中的任何一個方法, 那麼這個類就是描述器. 如果僅實現了__get__,就是非數據描述符 non-data descriptor 同時實現了除__get__以外的__set__或__delete__方法,就是數據描述符 data descriptor 如果一個類的類屬性設置爲描述器,那麼它被稱爲此描述器的owner屬主 描述器方法何時被觸發: 當屬主類中對是描述器的類屬性進行訪問時(即類似b.x),__get__方法被觸發 當屬主類中對是描述器的實例屬性通過'.'號賦值時,__set__方法被觸發
代碼二
class A: def __init__(self): self.a1 = 'a1' print('A.init') def __get__(self,instance,owner): print('A.__get__ {} {} {}'.format(self,instance,owner)) class B: x = A() def __init__(self): print('B.init') print('-' * 20) print(B.x) # print(B.x.a1) # AttributeError B.x爲None,None沒有a1屬性 print('*' * 20) b = B() # print(b.x.a1) # AttributeError B.x爲None,None沒有a1屬性 調用B類的類屬性,被A類__get__方法攔截,並返回值None
代碼三
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 class B: x = A() def __init__(self): print('B.init') print('-' * 20) print(B.x) print(B.x.a1) print('*' * 20) b = B() print(b.x) print(b.x.a1) 解決上例中的返回值爲None,將A類的實例返回,可成功調用A實例的a1屬性
代碼四
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 class B: x = A() print(x) def __init__(self): print('B.init') # self.x = 100 # 實例調用x屬性時,直接查實例自己的__dict__ self.x = A() # 實例調用x屬性時,不進入A類的__get__方法 print(self.x) print('-' * 20) print(B.x) # __get__ print(B.x.a1) # __get__ print('*' * 20) b = B() print(b.x) print(b.x.a1) 總結: 不論是實例還是類,只要是訪問了是描述器的類屬性, 都會被描述器的__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 def __set__(self,instance,value): print('A.__set__ {} {} {}'.format(self,instance,value)) class B: x = A() print(x) def __init__(self): print('B.init') self.x = 100 # self.x = A() # 同上面100結果類似 print(self.x) # print('-' * 20) # print(B.x) # print(B.x.a1) # print('*' * 20) b = B() # print(b.x) # print(b.x.a1) print(b.__dict__) print(B.__dict__) 屏蔽A類的__set__方法,實例的__dict__爲{'x': 100} 不屏蔽A類的__set__方法,實例的__dict__爲{} __set__方法本質將實例的__dict__的屬性名清空,從而達到數據描述器優先於查實例字典的假象
Python中的描述器
描述器在Python中應用非常廣泛 Python的方法(包括staticmethod()和classmethod()) 都實現爲非數據描述器. 因此,實例可以通過'.'號進行生成屬性. property()函數實現爲一個數據描述器.則實例不能使用'.'號進行賦值屬性.
示例
class A: @classmethod def foo(cls): pass @staticmethod def bar(): pass @property def z(self): return 5 def __init__(self): # 非數據描述器 self.foo = 100 self.bar = 200 # self.z = 300 # z屬性不能使用實例覆蓋 a = A() print(a.__dict__) print(A.__dict__)
練習
實現StaticMethod裝飾器,完成staticmethod裝飾器的功能
class StaticMethod: def __init__(self,fn): self._fn = fn def __get__(self,instance,owner): print(self,instance,owner) return self._fn class A: @StaticMethod # stmd = StaticMehtod(stmd) def stmd(): print('stmd') print(A.__dict__) A.stmd() # 類調用stmd屬性
實現ClassMethod裝飾器,完成classmethod裝飾器的功能
from functools import partial class ClassMethod: def __init__(self,fn): self._fn = fn def __get__(self,instance,owner): print(self,instance,owner) return partial(self._fn,owner) # 使用partial函數將類給作爲默認參數 class A: @ClassMethod # clsmd = ClassMethod(clsmd) def clsmd(cls): print('cls',cls.__name__) print(A.__dict__) A.clsmd()
類初始化的參數檢查
import inspect class Typed: def __init__(self,tp): self._tp = tp def __get__(self,instance,owner): pass def __set__(self,instance,value): if not isinstance(value,self._tp): raise ValueError(value) setattr(instance.__class__,self._name,value) def pcheck(cls): def wrapper(*args): sig = inspect.signature(cls) params = sig.parameters for i,(name,param) in enumerate(params.items()): if param.empty != param.annotation: # if not isinstance(args[i],param.annotation): # raise ValueError(args[i]) setattr(cls,name,Typed(param.annotation)) return cls(*args) return wrapper @pcheck class A: # a = Typed(str) # b = Typed(int) def __init__(self,a:str,b:int): self.a = a self.b = b A('1',2)
描述器結合裝飾實現
import inspect class Typed: def __init__(self,name,tp): self._name = name self._tp = tp def __get__(self,instance,owner): print('get',self,instance,owner) return instance.__dict__[self._name] def __set__(self,instance,value): print('set',self,instance,value) if not isinstance(value,self._tp): raise ValueError(value) instance.__dict__[self._name] = value class A: a = Typed('a',str) b = Typed('b',int) def __init__(self,a:str,b:int): self.a = a self.b = b a = A('1',2) print(a.__dict__) # print(type(a.a),type(a.b)) print(a.a)
描述器實現
import inspect def pcheck(cls): def wrapper(*args): sig = inspect.signature(cls) params = sig.parameters for i,(_,param) in enumerate(params.items()): if param.empty != param.annotation: if not isinstance(args[i],param.annotation): raise ValueError(args[i]) return cls(*args) return wrapper @pcheck # A = pcheck(A) class A: def __init__(self,a:str,b:int): self.a = a self.b = b A('1','2')
裝飾器版本
class A:
def __init__(self,a:str,b:int):
if not (isinstance(a,str) and isinstance(b,int)):
raise ValueError(a,b)
else:
self.a = a
self.b = b
A('1',2)直接參數檢查
思路:
實現參數檢查的本質是判斷傳入的參數是否符合形參定義的類型,也就是用isinstance進行判斷.
因此參數檢查的不同實現的區別在於在哪些地方攔截傳入的參數,來進行檢查.
上述實現的攔截地方:
在類初始化時,在對實例屬性賦值之前攔截
使用裝飾器,和inspect模塊,在實例化之前進行參數檢查
使用描述器,在初始化時對實例屬性設置時,觸發描述器的__set__方法,在__set__方法中進行參數檢查,再對其實例的類添加類屬性
(如果添加在實例上,則會遞歸調用回到__set__方法)
使用裝飾器獲取參數註解,給類添加有描述器的類屬性,再通過描述器的方式進行參數檢查