面向對象學習目錄 1 面向對象介紹 2 類、實例、屬性、方法詳解 3 面向過程與面向對象進一步比較 4 類與對象 5 屬性查找與綁定方法 6 小結 7 繼承與派生 8 組合 9 抽象類 10 多態 11 封裝 12 綁定方法與非綁定方法 13 內置方法(上) 14 內置方法(中)之描述符 15 內置 ...
面向對象學習目錄
六、描述符(__get__,__set__,__delete__) 1 描述符是什麼: 描述符本質就是一個新式類,在這個新式類中,至少實現了__get__(),__set__(),__delete__()中的一個,這也被稱為描述符協議 __get__():調用一個屬性時,觸發 __set__():為一個屬性賦值時,觸發 __delete__():採用del刪除屬性時,觸發 定義一個描述符
1 class Foo: # 在python3中Foo是新式類,它實現了三種方法,這個類就被稱作一個描述符 2 def __get__(self, instance, owner): 3 pass 4 def __set__(self, instance, value): 5 pass 6 def __delete__(self, instance): 7 pass
2 描述符是乾什麼的: 描述符的作用是用來代理另外一個類的屬性的(必須把描述符定義成這個類的類屬性,不能定義到構造函數中) 引子:描述符類產生的實例進行屬性操作並不會觸發三個方法的執行
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 #包含這三個方法的新式類稱為描述符,由這個類產生的實例進行屬性的調用/賦值/刪除,並不會觸發這三個方法 10 f1=Foo() 11 f1.name='egon' 12 f1.name 13 del f1.name 14 #疑問:何時,何地,會觸發這三個方法的執行
描述符應用之何時?何地?
1 # 描述符Str 2 class Str: 3 def __get__(self, instance, owner): 4 print('Str調用') 5 def __set__(self, instance, value): 6 print('Str設置...') 7 def __delete__(self, instance): 8 print('Str刪除...') 9 10 # 描述符Int 11 class Int: 12 def __get__(self, instance, owner): 13 print('Int調用') 14 def __set__(self, instance, value): 15 print('Int設置...') 16 def __delete__(self, instance): 17 print('Int刪除...') 18 19 class People: 20 name=Str() 21 age=Int() 22 def __init__(self,name,age): #name被Str類代理,age被Int類代理, 23 self.name=name 24 self.age=age 25 26 # 何地?:定義成另外一個類的類屬性 27 28 # 何時?:且看下列演示 29 30 p1=People('alex',18) 31 32 # 描述符Str的使用 33 p1.name 34 p1.name='egon' 35 del p1.name 36 37 # 描述符Int的使用 38 p1.age 39 p1.age=18 40 del p1.age 41 42 # 我們來瞅瞅到底發生了什麼 43 print(p1.__dict__) 44 print(People.__dict__) 45 46 # 補充 47 print(type(p1) == People) #type(obj)其實是查看obj是由哪個類實例化來的 48 print(type(p1).__dict__ == People.__dict__)
3 描述符分兩種 一 數據描述符: 至少實現了__get__()和__set__()
1 class Foo: 2 def __set__(self, instance, value): 3 print('set') 4 def __get__(self, instance, owner): 5 print('get')
二 非數據描述符: 沒有實現__set__()
1 class Foo: 2 def __get__(self, instance, owner): 3 print('get')
4 註意事項: 一 描述符本身應該定義成新式類,被代理的類也應該是新式類 二 必須把描述符定義成這個類的類屬性,不能為定義到構造函數中 三 要嚴格遵循該優先順序,優先順序由高到底分別是 1.類屬性 2.數據描述符 3.實例屬性 4.非數據描述符 5.找不到的屬性觸發__getattr__() 類屬性>數據描述符
1 #描述符Str 2 class Str: 3 def __get__(self, instance, owner): 4 print('Str調用') 5 def __set__(self, instance, value): 6 print('Str設置...') 7 def __delete__(self, instance): 8 print('Str刪除...') 9 10 class People: 11 name=Str() 12 def __init__(self,name,age): #name被Str類代理,age被Int類代理, 13 self.name=name 14 self.age=age 15 16 17 #基於上面的演示,我們已經知道,在一個類中定義描述符它就是一個類屬性,存在於類的屬性字典中,而不是實例的屬性字典 18 19 #那既然描述符被定義成了一個類屬性,直接通過類名也一定可以調用吧,沒錯 20 People.name #恩,調用類屬性name,本質就是在調用描述符Str,觸發了__get__() 21 22 People.name='egon' #那賦值呢,我去,並沒有觸發__set__() 23 del People.name #趕緊試試del,我去,也沒有觸發__delete__() 24 #結論:描述符對類沒有作用-------->傻逼到家的結論 25 26 ''' 27 原因:描述符在使用時被定義成另外一個類的類屬性,因而類屬性比二次加工的描述符偽裝而來的類屬性有更高的優先順序 28 People.name #恩,調用類屬性name,找不到就去找描述符偽裝的類屬性name,觸發了__get__() 29 30 People.name='egon' #那賦值呢,直接賦值了一個類屬性,它擁有更高的優先順序,相當於覆蓋了描述符,肯定不會觸發描述符的__set__() 31 del People.name #同上 32 '''
數據描述符>實例屬性
1 #描述符Str 2 class Str: 3 def __get__(self, instance, owner): 4 print('Str調用') 5 def __set__(self, instance, value): 6 print('Str設置...') 7 def __delete__(self, instance): 8 print('Str刪除...') 9 10 class People: 11 name=Str() 12 def __init__(self,name,age): #name被Str類代理,age被Int類代理, 13 self.name=name 14 self.age=age 15 16 17 p1=People('egon',18) 18 19 #如果描述符是一個數據描述符(即有__get__又有__set__),那麼p1.name的調用與賦值都是觸發描述符的操作,於p1本身無關了,相當於覆蓋了實例的屬性 20 p1.name='egonnnnnn' 21 p1.name 22 print(p1.__dict__)#實例的屬性字典中沒有name,因為name是一個數據描述符,優先順序高於實例屬性,查看/賦值/刪除都是跟描述符有關,與實例無關了 23 del p1.name
實例屬性>非數據描述符
1 class Foo: 2 def func(self): 3 print('我胡漢三又回來了') 4 5 f1=Foo() 6 f1.func() 7 #調用類的方法,也可以說是調用非數據描述符 8 #函數是一個非數據描述符對象(一切皆對象麽) 9 print(dir(Foo.func)) 10 print(hasattr(Foo.func,'__set__')) 11 print(hasattr(Foo.func,'__get__')) 12 print(hasattr(Foo.func,'__delete__')) 13 #有人可能會問,描述符不都是類麽,函數怎麼算也應該是一個對象啊,怎麼就是描述符了 14 #笨蛋哥,描述符是類沒問題,描述符在應用的時候不都是實例化成一個類屬性麽 15 #函數就是一個由非描述符類實例化得到的對象 16 #沒錯,字元串也一樣 17 18 f1.func='這是實例屬性啊' 19 print(f1.func) 20 21 del f1.func #刪掉了非數據 22 f1.func()
再次驗證:實例屬性>非數據描述符
1 class Foo: 2 def __set__(self, instance, value): 3 print('set') 4 def __get__(self, instance, owner): 5 print('get') 6 7 class Room: 8 name=Foo() 9 def __init__(self,name,width,length): 10 self.name=name 11 self.width=width 12 self.length=length 13 14 15 #name是一個數據描述符,因為name=Foo()而Foo實現了get和set方法,因而比實例屬性有更高的優先順序 16 #對實例的屬性操作,觸發的都是描述符的 17 r1=Room('廁所',1,1) 18 r1.name 19 r1.name='廚房'
1 class Foo: 2 def __get__(self, instance, owner): 3 print('get') 4 5 class Room: 6 name=Foo() 7 def __init__(self,name,width,length): 8 self.name=name 9 self.width=width 10 self.length=length 11 12 13 #name是一個非數據描述符,因為name=Foo()而Foo沒有實現set方法,因而比實例屬性有更低的優先順序 14 #對實例的屬性操作,觸發的都是實例自己的 15 r1=Room('廁所',1,1) 16 r1.name 17 r1.name='廚房'
非數據描述符>找不到
1 class Foo: 2 def func(self): 3 print('我胡漢三又回來了') 4 5 def __getattr__(self, item): 6 print('找不到了當然是來找我啦',item) 7 8 f1=Foo() 9 f1.xxxxxxxxxxx
5 描述符使用 眾所周知,python是弱類型語言,即參數的賦值沒有類型限制,下麵我們通過描述符機制來實現類型限制功能 牛刀小試
1 class Str: 2 def __init__(self,name): 3 self.name=name 4 def __get__(self, instance, owner): 5 print('get--->',instance,owner) 6 return instance.__dict__[self.name] 7 def __set__(self, instance, value): 8 print('set--->',instance,value) 9 instance.__dict__[self.name]=value 10 def __delete__(self, instance): 11 print('delete--->',instance) 12 instance.__dict__.pop(self.name) 13 14 15 class People: 16 name=Str('name') 17 def __init__(self,name,age,salary): 18 self.name=name 19 self.age=age 20 self.salary=salary 21 22 p1=People('egon',18,3231.3) 23 24 #調用 25 print(p1.__dict__) 26 p1.name 27 28 #賦值 29 print(p1.__dict__) 30 p1.name='egonlin' 31 print(p1.__dict__) 32 33 #刪除 34 print(p1.__dict__) 35 del p1.name 36 print(p1.__dict__)
拔刀相助
1 class Str: 2 def __init__(self,name): 3 self.name=name 4 def __get__(self, instance, owner): 5 print('get--->',instance,owner) 6 return instance.__dict__[self.name] 7 8 def __set__(self, instance, value): 9 print('set--->',instance,value) 10 instance.__dict__[self.name]=value 11 def __delete__(self, instance): 12 print('delete--->',instance) 13 instance.__dict__.pop(self.name) 14 15 16 class People: 17 name=Str('name') 18 def __init__(self,name,age,salary): 19 self.name=name 20 self.age=age 21 self.salary=salary 22 23 #疑問:如果我用類名去操作屬性呢 24 People.name #報錯,錯誤的根源在於類去操作屬性時,會把None傳給instance
修訂__get__方法
1 class Str: 2 def __init__(self,name): 3 self.name=name 4 def __get__(self, instance, owner): 5 print('get--->',instance,owner) 6 if instance is None: 7 return self 8 return instance.__dict__[self.name] 9 10 def __set__(self, instance, value): 11 print('set--->',instance,value) 12 instance.__dict__[self.name]=value 13 def __delete__(self, instance): 14 print('delete--->',instance) 15 instance.__dict__.pop(self.name) 16 17 18 class People: 19 name=Str('name') 20 def __init__(self,name,age,salary): 21 self.name=name 22 self.age=age 23 self.salary=salary 24 print(People.name) #完美,解決
磨刀霍霍
1 class Str: 2 def __init__(self,name,expected_type): 3 self.name=name 4 self.expected_type=expected_type 5 def __get__(self, instance, owner): 6 print('get--->',instance,owner) 7 if instance is None: 8 return self 9 return instance.__dict__[self.name] 10 11 def __set__(self, instance, value): 12 print('set--->',instance,value) 13 if not isinstance(value,self.expected_type): #如果不是期望的類型,則拋出異常 14 raise TypeError('Expected %s' %str(self.expected_type)) 15 instance.__dict__[self.name]=value 16 def __delete__(self, instance): 17 print('delete--->',instance) 18 instance.__dict__.pop(self.name) 19 20 21 class People: 22 name=Str('name',str) #新增類型限制str 23 def __init__(self,name,age,salary): 24 self.name=name 25 self.age=age 26 self.salary=salary 27 28 p1=People(123,18,3333.3) #傳入的name因不是字元串類型而拋出異常
大刀闊斧
1 class Typed: 2 def __init__(self,name,expected_type): 3 self.name=name 4 self.expected_type=expected_type 5 def __get__(self, instance, owner): 6 print('get--->',instance,owner) 7 if instance is None: 8 return self 9 return instance.__dict__[self.name] 10 11 def __set__(self, instance, value): 12 print('set--->',instance,value) 13 if not isinstance(value,self.expected_type): 14 raise TypeError('Expected %s' %str(self.expected_type)) 15 instance.__dict__[self.name]=value 16 def __delete__(self, instance): 17 print('delete--->',instance) 18 instance.__dict__.pop(self.name) 19 20 21 class People: 22 name=Typed('name',str) 23 age=Typed('name',int) 24 salary=Typed('name',float) 25 def __init__(self,name,age,salary): 26 self.name=name 27 self.age=age 28 self.salary=salary 29 30 p1=People(123,18,3333.3) 31 p1=People('egon','18',3333.3) 32 p1=People('egon',18,3333)
大刀闊斧之後我們已然能實現功能了,但是問題是,如果我們的類有很多屬性,你仍然採用在定義一堆類屬性的方式去實現,low,這時候我需要教你一招:獨孤九劍
類的裝飾器:無參1 def decorate(cls): 2 print('類的裝飾器開始運行啦------>') 3 return cls 4 5 @decorate # 無參:People=decorate(People) 6 class People: 7 def __init__(self,name,age,salary): 8 self.name=name 9 self.age=age 10 self.salary=salary 11 12 p1=People('egon',18,3333.3)
類的裝飾器:有參
def typeassert(**kwargs): def decorate(cls): print('類的裝飾器開始運行啦------>',kwargs) return cls return decorate @typeassert(name=str,age=int,salary=float) #有參: 1.運行typeassert(...)返回結果是decorate,此時參數都傳給kwargs # 2.People=decorate(People) class People: def __init__(self,name,age,salary): self.name=name self.age=age self.salary=salary p1=People('egon',18,3333.3)
終極大招
刀光劍影1 class Typed: 2 def __init__(self,name,expected_type): 3 self.name=name 4 self.expected_type=expected_type 5 def __get__(self, instance, owner): 6 print('get--->',instance,owner) 7 if instance is None: 8 return self 9 return instance.__dict__[self.name] 10 11 def __set__(self, instance, value): 12 print('set--->',instance,value) 13 if not isinstance(value,self.expected_type): 14 raise TypeError('Expected %s' %str(self.expected_type)) 15 instance.__dict__[self.name]=value 16 def __delete__(self, instance): 17 print('delete--->',instance) 18 instance.__dict__.pop(self.name) 19 20 def typeassert(**kwargs): 21 def decorate(cls): 22 print('類的裝飾器