描述符
描述符(__get__,__set__,__delete__) # 這裏着重描述了python的底層實現原理python
一、 描述符是什麼:描述符本質就是一個新式類,在這個新式類中,至少實現了__get__(),__set__(),__delete__()中的一個,這也被稱爲描述符協議。
__get__():調用一個屬性時,觸發
__set__():爲一個屬性賦值時,觸發
__delete__():採用del刪除屬性時,觸發框架
1 class Foo: #在python3中Foo是新式類,它實現了三種方法,這個類就被稱做一個描述符 2 def __get__(self,instance,owner): 3 print('get方法') 4 def __set__(self, instance, value): 5 print('set方法') 6 def __delete__(self, instance): 7 print('delete方法')
二、描述符是幹什麼的:描述符的做用是用來代理另一個類的屬性的(必須把描述符定義成這個類的類屬性,不能定義到構造函數中)ide
class Foo: def __get__(self,instance,owner): print('===>get方法') def __set__(self, instance, value): print('===>set方法') def __delete__(self, instance): print('===>delete方法') #包含這三個方法的新式類稱爲描述符,由這個類產生的實例進行屬性的調用/賦值/刪除,並不會觸發這三個方法 f1=Foo() f1.name='egon' print(f1.name) del f1.name #疑問:什麼時候,何地,會觸發這三個方法的執行
三、描述符應用在何時,什麼地方函數
class D: def __get__(self, instance, owner): print("-->get") def __set__(self, instance, value): print("-->set") def __delete__(self, instance): print("-->delete") class E: e = D() # 描述誰? ee = E() ee.y = 10 # 此時描述的是e y則不會被描述 ee.e # 訪問e屬性,則會觸發__get__ ee.e = 2 # 爲e進行賦值操做,則會觸發__set__ del ee.e # 刪除e的屬性,則會觸發__delete__ # print(ee.__dict__)
四、描述符分爲倆種形式。工具
a.數據描述符(至少實現了__get__()和__set__()兩種方法)post
class Foo: def __set__(self, instance, value): print('set') def __get__(self, instance, owner): print('get')
b.非數據描述符(沒有實現__set__()方法)優化
1 class Foo: 2 def __get__(self, instance, owner): 3 print('get')
注意事項:
1、描述符自己應該定義成新式類,被代理的類也應該是新式類
2、必須把描述符定義成另一個類觸發的類屬性,不能爲定義到構造函數spa
五、嚴格遵循描述符的優先級別,由高到低3d
a.類屬性—》b.數據數據描述符—》c.實例屬性—》d.非數據描述符—》e.找不到的屬性觸發__getattr__()代理
1 class Foo: 2 def __get__(self,instance,owner): 3 print('===>get方法') 4 def __set__(self, instance, value): 5 print('===>set方法') 6 def __delete__(self, instance): 7 print('===>delete方法') 8 9 class Bar: 10 x=Foo() #調用foo()屬性,會觸發get方法 11 12 print(Bar.x) #類屬性比描述符有更高的優先級,會觸發get方法 13 Bar.x=1 #本身定義了一個類屬性,並賦值給x,跟描述符沒有關係,因此不會觸發描述符的方法 14 # print(Bar.__dict__) 15 print(Bar.x) 16 17 18 ===>get方法 19 None 20 1
#有get,set就是數據描述符,數據描述符比實例屬性有更高的優化級 class Foo: def __get__(self,instance,owner): print('===>get方法') def __set__(self, instance, value): print('===>set方法') def __delete__(self, instance): print('===>delete方法') class Bar: x = Foo() # 調用foo()屬性,會觸發get方法 b1=Bar() #在本身的屬性字典裏面找,找不到就去類裏面找,會觸發__get__方法 b1.x #調用一個屬性的時候觸發get方法 b1.x=1 #爲一個屬性賦值的時候觸發set方法 del b1.x #採用del刪除屬性時觸發delete方法 ===>get方法 ===>set方法 ===>delete方法
1 #類屬性>數據描述符>實例屬性 2 3 class Foo: 4 def __get__(self,instance,owner): 5 print('===>get方法') 6 def __set__(self, instance, value): 7 print('===>set方法') 8 def __delete__(self, instance): 9 print('===>delete方法') 10 11 class Bar: 12 x = Foo() #調用foo()屬性,會觸發get方法 13 14 b1=Bar() #實例化 15 Bar.x=11111111111111111 #不會觸發get方法 16 b1.x #會觸發get方法 17 18 del Bar.x #已經給刪除,因此調用不了!報錯:AttributeError: 'Bar' object has no attribute 'x' 19 b1.x
#實例屬性>非數據描述符 class Foo: def __get__(self,instance,owner): print('===>get方法') class Bar: x = Foo() b1=Bar() b1.x=1 print(b1.__dict__) #在本身的屬性字典裏面,{'x': 1} {'x': 1}
1 #非數據描述符>找不到 2 3 class Foo: 4 def __get__(self,instance,owner): 5 print('===>get方法') 6 7 class Bar: 8 x = Foo() 9 def __getattr__(self, item): 10 print('------------>') 11 12 b1=Bar() 13 b1.xxxxxxxxxxxxxxxxxxx #調用沒有的xxxxxxx,就會觸發__getattr__方法 14 15 16 ------------> #解發__getattr__方法
六、關於描述符的應用(類型檢測的應用)
class Typed: def __get__(self, instance,owner): print('get方法') print('instance參數【%s】' %instance) print('owner參數【%s】' %owner) # owner是顯示對象是屬於誰擁有的 def __set__(self, instance, value): print('set方法') print('instance參數【%s】' %instance) # instance是被描述類的對象(實例) print('value參數【%s】' %value) # value是被描述的值 def __delete__(self, instance): print('delete方法') print('instance參數【%s】'% instance) class People: name=Typed() def __init__(self,name,age,salary): self.name=name #觸發的是代理 self.age=age self.salary=salary p1=People('alex',13,13.3) #'alex' #觸發set方法 p1.name #觸發get方法,沒有返回值 p1.name='age' #觸發set方法 print(p1.__dict__) #{'salary': 13.3, 'age': 13} # 由於name已經被描述,因此實例的屬性字典並不存在name # 固然也說明一點實例屬性的權限並無數據描述符的權限大 set方法 instance參數【<__main__.People object at 0x000001CECBFF0080>】 value參數【alex】 get方法 instance參數【<__main__.People object at 0x000001CECBFF0080>】 owner參數【<class '__main__.People'>】 set方法 instance參數【<__main__.People object at 0x000001CECBFF0080>】 value參數【age】 {'salary': 13.3, 'age': 13}
class Foo: def __init__(self,key,pd_type): self.key = key self.pd_type = pd_type def __get__(self, instance, owner): print("get") return instance.__dict__[self.key] # 返回值是 instace對象屬性字典self.key所對應的值 def __set__(self, instance, value): print(value) # 輸出value所對應的值 if not isinstance(value,self.pd_type): # 判斷被描述的值 是否 屬於這個類的 raise TypeError("%s 傳入的類型不是%s" %(value,self.pd_type)) # 爲否 則拋出類型異常 instance.__dict__[self.key] = value # True 對instance對象的屬性字典進行賦值操做 def __delete__(self, instance): print("delete") instance.__dict__.pop(self.key) # 若是進行刪除操做,也是對instance對象的屬性字典進行刪除操做 class Sea: name = Foo("name",str) # 向描述符傳入倆個值 history = Foo("history",int) def __init__(self,name,addr,history): self.name = name self.addr = addr self.history = history s1 = Sea("北冰洋","北半球",10000) print(s1.__dict__) print(s1.name) # 對被描述的屬性進行訪問,觸發__get__ 北冰洋 10000 {'addr': '北半球', 'history': 10000, 'name': '北冰洋'} get 北冰洋
# 描述符和類裝飾器的結合使用 class Foo: """ 描述符 """ def __init__(self,key,pd_type): self.key = key self.pd_type = pd_type def __get__(self, instance, owner): print("get") return instance.__dict__[self.key] def __set__(self,instance,value): """ instance是對象(實例) """ print("set") if not isinstance(value,self.pd_type): # 若是判斷類型爲False raise TypeError("%s 傳入的類型不是%s" %(value,self.pd_type)) # 拋出異常 instance.__dict__[self.key] = value #操做對象(實例)的屬性字典設置值 def __delete__(self, instance): print("delete") instance.__dict__.pop(self.key) def Typed(**kwargs): def func(obj): for key,value in kwargs.items(): # 遍歷字典的鍵值對 setattr(obj,key,Foo(key,value)) # 爲Sea的屬性字典設置值。 並執行Foo 給Foo傳入倆個值 # print("--->" ,obj) return obj # func的返回值是Sea return func @Typed(name=str,addr=str,history=int) # Typed函數運行結束實際上就是 @func --> sea=func(Sea) # Typed運行,並將參數所有傳給kwargs class Sea: def __init__(self,name,addr,history): self.name = name self.addr = addr self.history = history s1 = Sea("大西洋","地球",10000) print(s1.__dict__) print(Sea.__dict__) # 此時的name,addr,history均被Foo所描述
七、描述符總結
描述符是能夠實現大部分python類特性中的底層魔法,包括@classmethod,@staticmethd,@property甚至是__slots__屬性
描述符是不少高級庫和框架的重要工具之一,描述符一般是使用到裝飾器或者元類的大型框架中的一個組件.
a.利用描述符原理完成一個自定製@property,實現延遲計算(本質就是把一個函數屬性利用裝飾器原理作成一個描述符:類的屬性字典中函數名爲key,value爲描述符類產生的對象)
class Room: def __init__(self,name,width,length): self.name=name self.width=width self.length=length @property def area(self): return self.width * self.length r1=Room(Tom',1,1) print(r1.area)
# 僞造的property class Wzproperty: def __init__(self,func): self.func = func def __get__(self, instance, owner): """ 若是類去調用 instance 爲None""" print("get") if instance is None: return self setattr(instance,self.func.__name__,self.func(instance)) # 給實例字典設置值,避免重複計算 return self.func(instance) class Sea: def __init__(self,name,history,speed): self.name = name self.history = history self.speed = speed @Wzproperty # test = Wzptoperty(test) def test(self): return self.history * self.speed s1 = Sea("大西洋",10,20) # print(Sea.__dict__) # print(Sea.test) # 若是類去調用 描述符的instance 此時是None print(s1.test) print(s1.test) # 這一次就不會觸發描述符,由於實例屬性字典就有 """由於有了爲實例的屬性字典設置告終果。因此會率先從本身的屬性字典找 其次觸發非數據描述符,同時也聲明瞭實例屬性的權限大於非數據描述。 若是給描述符+__set__,描述符就變爲數據描述符,根據權限實例再去用不會先去 本身的屬性字典,而是觸發描述符的操做""" print(s1.__dict__) 控制檯輸出 get 200 200 {'test': 200, 'speed': 20, 'name': '大西洋', 'history': 10} # 實例的屬性字典
# 僞造的classmethod class Wzclassmethod: def __init__(self,func): self.func = func def __get__(self, instance, owner): print("get") def bar(): return self.func(owner) # test(Sea) return bar def __set__(self, instance, value): print("set") class Sea: long = 10 kuan = 20 @Wzclassmethod # test = Wzclassmethod(test) def test(cls): print("長%s 寬%s" %(cls.long,cls.kuan)) Sea.test()
# 僞造的staticmethod import hashlib,time class Wzstaticmethod: def __init__(self,func): self.func = func def __set__(self, instance, value): print("set") def __get__(self, instance, owner): print("get") def bar(): if instance is None: return self.func() return bar def __delete__(self, instance): print("delete") class Onepiece: def __init__(self): pass @Wzstaticmethod # test = Wzstaticmethod(test) def test(x=1): hash = hashlib.md5() hash.update(str(time.time()).encode("utf-8")) filename = hash.hexdigest() print(filename) return filename # print(Onepiece.__dict__) Onepiece.test()
類裝飾器
一、基本框架
def deco(func): print('===================') return func #fuc=test @deco #test=deco(test) def test(): print('test函數運行') test()
def deco(obj): print('============',obj) obj.x=1 #增長屬性 obj.y=2 obj.z=3 return obj @deco #Foo=deco(Foo) #@deco語法糖的基本原理 class Foo: pass print(Foo.__dict__) #加到類的屬性字典中 輸出 ============ <class '__main__.Foo'> {'__module__': '__main__', 'z': 3, 'x': 1, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, 'y': 2}
def Typed(**kwargs): def deco(obj): obj.x=1 obj.y=2 obj.z=3 return obj print('====>',kwargs) return deco @Typed(x=2,y=3,z=4) #typed(x=2,y=3,z=4)--->deco 會覆蓋原有值 class Foo: pass
def Typed(**kwargs): def deco(obj): for key,val in kwargs.items(): setattr(obj,key,val) return obj return deco @Typed(x=1,y=2,z=3) #typed(x=1,y=2,z=3)--->deco class Foo: pass print(Foo.__dict__) @Typed(name='egon') class Bar: pass print(Bar.name) 控制檯輸出 {'y': 2, '__dict__': <attribute '__dict__' of 'Foo' objects>, 'z': 3, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__module__': '__main__', 'x': 1, '__doc__': None} egon
元類
元類(metaclass)
class Foo: pass f1=Foo() #f1是經過Foo類實例化的對象
python中一切皆是對象,類自己也是一個對象,當使用關鍵字class的時候,python解釋器在加載class的時候就會建立一個對象(這裏的對象指的是類而非類的實例)
上例能夠看出f1是由Foo這個類產生的對象,而Foo自己也是對象,那它又是由哪一個類產生的呢?
#type函數能夠查看類型,也能夠用來查看對象的類,兩者是同樣的 print(type(f1)) # 輸出:<class '__main__.Foo'> 表示,obj 對象由Foo類建立 print(type(Foo)) # 輸出:<type 'type'>
一、辣麼,什麼是元類?
- 元類是類的類,是類的模板
- 元類是用來控制如何建立類的,正如類是建立對象的模板同樣
- 元類的實例爲類,正如類的實例爲對象(f1對象是Foo類的一個實例,Foo類是 type 類的一個實例)
- type是python的一個內建元類,用來直接控制生成類,python中任何class定義的類其實都是type類實例化的對象
建立類有倆種方式
class Foo: def func(self): print('from func')
def func(self): print('from func') x=1 Foo=type('Foo',(object,),{'func':func,'x':1}) type要接收三個參數 1、將要建立的類名 2、繼承的類 三、類的屬性字典
# 方式1 class Foo: pass # 方式2 Bar = type("Bar",(object,),{}) print(Foo.__dict__) print(Bar.__dict__) 控制檯輸出 {'__module__': '__main__', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__dict__': <attribute '__dict__' of 'Foo' objects>} {'__module__': '__main__', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Bar' objects>, '__dict__': <attribute '__dict__' of 'Bar' objects>}
二、一個類沒有聲明本身的元類,默認它的元類就是type,除了使用元類type,用戶也能夠經過繼承type來自定義元類(順便咱們也能夠瞅一瞅元類如何控制類的建立,工做流程是什麼)
class Mytype(type): def __init__(self,a,b,c): print(self) print(a) print(b) print(c) def __call__(self, *args, **kwargs): print("call") class Slamdunk(metaclass=Mytype): # Mytype("Slamdunk",(object,),{}) 實際上就是這麼作,可是傳了4個參數 # 聲明Foo類由Mytype建立,聲明本身的元類 def __init__(self,name): self.name = name s1 = Slamdunk("櫻木花道") # 根據python一切皆對象,Slamdunk() 本質上就是在觸發建立 Slamdunk類的 元類的__call__ 控制檯輸出 <class '__main__.Slamdunk'> # 元類建立的實例(對象) Slamdunk # 實例名 () # 繼承的類,在python3中都默認繼承object,即都爲新式類 {'__qualname__': 'Slamdunk', '__init__': <function Slamdunk.__init__ at 0x000002106AFBF840>, '__module__': '__main__'} # 實例類的屬性字典 call # 實例+() 觸發了元類的__call__方法
class Mytype(type): def __init__(self,a,b,c): print(self) def __call__(self, *args, **kwargs): # 傳的值是怎麼傳進去的,就去怎麼接收 print("call") obj = object.__new__(self) # 生成一個實例 self.__init__(obj,*args,**kwargs) # 這裏的self是Mytype產生的實例,這一步觸發 Slamdunk 的構造方法 return obj # __call__方法下的返回值是 self 產生的實例 賦值給s1 class Slamdunk(metaclass=Mytype): # Slamdunk = Mytype("Slamdunk",(object,),{}) 實際上就是這麼作,可是傳了4個參數 # 聲明Foo類由Mytype建立,聲明本身的元類 # 觸發元類的__init__(元類的構造方法) def __init__(self,name): self.name = name s1 = Slamdunk("櫻木花道") # 根據python一切皆對象,Slamdunk() 本質上就是在觸發建立 Slamdunk類的 元類的__call__ print(s1.__dict__) # 能夠訪問到實例對象的屬性字典
class Mytype(type): def __init__(self,a,b,c): print(self) def __call__(self, *args, **kwargs): obj = object.__new__(self) self.__init__(obj,*args,**kwargs) return obj class Slamdunk(metaclass=Mytype): def __init__(self,name): self.name = name s1 = Slamdunk("櫻木花道") print(s1.__dict__) 控制檯輸出 <class '__main__.Slamdunk'> {'name': '櫻木花道'} # 能夠加斷點體驗