python基礎之面向對象(描述符、類裝飾器及元類)
描述符
描述符(__get__,__set__,__delete__) # 這里着重描述了python的底層實現原理
1、 描述符是什么:描述符本質就是一個新式類,在這個新式類中,至少實現了__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方法')
2、描述符是干什么的:描述符的作用是用來代理另外一個類的屬性的(必須把描述符定義成這個類的類屬性,不能定義到構造函數中)

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 #疑問:何時,何地,會觸發這三個方法的執行
3、描述符應用在什么時候,什么地方

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__)
4、描述符分為倆種形式。
a.數據描述符(至少實現了__get__()和__set__()兩種方法)

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')
注意事項:
一、描述符本身應該定義成新式類,被代理的類也應該是新式類
二、必須把描述符定義成另外一個類觸發的類屬性,不能為定義到構造函數
5、嚴格遵循描述符的優先級別,由高到低
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__方法
6、關於描述符的應用(類型檢測的應用)

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 北冰洋

7、描述符總結
描述符是可以實現大部分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()
類裝飾器
1、基本框架

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'>
1、辣么,什么是元類?
- 元類是類的類,是類的模板
- 元類是用來控制如何創建類的,正如類是創建對象的模板一樣
- 元類的實例為類,正如類的實例為對象(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、繼承的類 3、類的屬性字典

# 方式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>}
2、一個類沒有聲明自己的元類,默認它的元類就是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': '櫻木花道'} # 可以加斷點體驗