1.描述器的表現
用到三個魔術方法,__get__(), __set__(), __delete__() 方法簽名如下 object.__get__(self,instance,owner) object.__set__(self,instance,value) object.__delete(self,instance) self指代當前實例,調用者 instance是owner的實例 owner是屬性的所輸的類
#描述器A調用並賦值給了類B的屬性,當調用類B或者實例b時,去類A執行__get__()函數,類調用instance返回none,實例調用返回實例 #執行順序和類,實例字典無關 class A: def __init__(self): print(2,'A init') def __set__(self, instance, value): print(3,self,instance,value) def __get__(self, instance, owner): #return值,將會影響b.x or B.x的調用屬性 print(4,self,instance,owner) class B: x = A() def __init__(self): print(1,'B init') b = B() #output 2->1 b.x #output 4 <__main__.A object at 0x047B09D0> <__main__.B object at 0x047BB350> <class '__main__.B'> B.x #output 4 <__main__.A object at 0x047B09D0> None <class '__main__.B'>
此時訪問b.x.a1 B.x.a1都會報錯 AttributeError:Nonetype
問題出在__get__的返回值,修改為 return self 返回A的實例就具備了a1屬性 返回正常
class B: x = A() def __init__(self): self.b1 = A() print(1,'B init') b = B() #output 2->1 print(b.b1) #output <__main__.A object at 0x03000990> 沒有觸發__get__的打印 從運行結果可以看出,只有類屬性是類的實例才行
2.描述其的定義
python中,一個類實現了__get__,__set__.__delete__的三個方法中的任意一個就是描述器 1.如果僅僅實現了__get__.就是非數據描述器 non-data descriptor 2.同時實現了__get__,__set__,就是數據描述器,data descriptor 如果一個類的類屬性,設置為描述器,那么這個類被稱為owner屬主,method也是類的屬性
class A: def __init__(self): self.a1 = 'a1' print(2,'A init') def __get__(self, instance, owner): print(4,self,instance,owner) return self class B: x = A() def __init__(self): self.x = 'b1' #如果描述器定義了__set__,此時b1就是value print(1,'B init') b = B() #output 2->1 print(B.x) #output 4 <__main__.A object at 0x04EEB350> None <class '__main__.B'> ;;;; return <__main__.A object at 0x02E8B350> print(B.x.a1) #output 4 <__main__.A object at 0x02E8B350> None <class '__main__.B'> ;;;;return a1 print(b.x) #return b1 訪問到了實例的屬性,而不是描述器 print(b.x.a1) #AttributeError 'str object has no Attribute
在非數據描述器中,owner實例的屬性 會被實例調用,而不是訪問__get__描述器
#添加了set方法 對比上個代碼,;數據描述器 class A: def __init__(self): self.a1 = 'a1' print(2,'A init') def __set__(self, instance, value): #當定義了set魔術方法后,B實例定義的實例屬性self.x = 'b1 不會在寫進實例字典,而是調用set方法 print(3,self,instance,value) # instance.__dict__['x']=value def __get__(self, instance, owner): #return值,將會影響b.x or B.x的調用屬性 print(4,self,instance,owner) # return instance.__dict__['x'] return self class B: x = A() def __init__(self): print(1,'B init') print("+++++++++++++++++++++++") self.x = 'b1' print("+++++++++++++++++++++++") b = B() #output 2->1->+ ->3-> + ;;實例化時候,self.x = 'b1'調用了set方法 print(b.x.a1) #return a1 直接調用get b.x = 100 #return a1 直接調用set print(b.__dict__) #實例字典為空 print(B.__dict__)
總結:實例的__dict__優先於非數據描述器;;;數據描述器優先於實例__dict__
2.1描述器查找順序和__dict__的關系
class A: def __init__(self): self.a1 = 'a1' print(2,'A init') def __set__(self, instance, value): print(3,self,instance,value) self.data = value print(self.data) def __get__(self, instance, owner): print(4,self,instance,owner) return self class B: x = A() def __init__(self): print(1,'B init') self.x = 'b.x' self.y = 'b.y' self.z = 'b.z' b = B() #output 2->1->+ ->3-> + ;;實例化時候,self.x = 'b1'調用了set方法 print(b.y) #return b.y print(b.x) print(B.__dict__) print(b.__dict__)#output {'y': 'b.y', 'z': 'b.z'} ;;;self.x 這里的x是x = A()所以調用set方法,而self.y self.z追加到字典
2.3練習
from functools import partial class StaticMethod: def __init__(self,fn): self.fn = fn def __get__(self, instance, owner): return self.fn class ClassMethod: def __init__(self,fn): self.fn = fn def __get__(self, instance, owner): # return self.fn(instance) return partial(self.fn,instance) class Test: @StaticMethod #st = staticmethod(st) 去調用__get__方法時,不用在考慮這個Test類了 裝飾器到描述器執行,調用時加()就返回值 def st(): #st = return self.fn print('st') @ClassMethod def ct(cls): #ct = return self.fn(instance) print('ct') t = Test() Test.st() t.st() t.ct() Test.ct()
class Typed: def __init__(self,name,type): self.name = name self.type = type def __set__(self, instance, value): if not isinstance(value,self.type): raise TypeError(value) instance.__dict__[self.name]=value def __get__(self, instance, owner): if instance is not None: return instance.__dict__[self.name] return self class Person: name = Typed('name',str) # 1 演變其他的基礎 age = Typed('age',int) def __init__(self,name:str,age:int): self.name = name # 2 self.age = age p = Person('tom',21) print(p.__dict__) #output {'name': 'tom', 'age': 21} print(p.age) #output 21 print(Person.age.name) #output age print(Person.name.name) #output name print(Typed.__dict__) print(Person.__dict__) # 1-->類屬性調用了類實例,2-->有set,此處調用set方法,因為數據描述器,所以字典為空,自己追加字典值, #get也需要從字典中獲取值
import inspect class Typed: def __init__(self,name,type): self.name = name self.type = type def __set__(self, instance, value): if not isinstance(value,self.type): raise TypeError(value) instance.__dict__[self.name]=value def __get__(self, instance, owner): if instance is not None: return instance.__dict__[self.name] return self def typeassert(cls): parmas = inspect.signature(cls).parameters for k,v in parmas.items(): if v.annotation != v.empty: setattr(cls,k,Typed(v,v.annotation)) return cls @typeassert class Person: def __init__(self,name:str,age:int): self.name = name # 2 self.age = age p = Person('tom',21)