python面向對象魔術方法補充
一、描述符
在 面向對象 編程中 定義一個(沒有定義方法)類:class person , 在這個類裏面,有name,age, heigth, weight,等等屬性, 這個類就可以看作一個對 person 的描述符,而具體的實例則是具體的“被描述物”。
而在python中,描述符本質就是一個新式類,在這個新式類中,至少實現了__get__(),__set__(),__delete__()中的一個,這也被稱為描述符協議。
__get__():調用一個屬性時,觸發
__set__():為一個屬性賦值時,觸發d
__delete__():采用del刪除屬性時,觸發
class Foo: #在python3中Foo是新式類,它實現了三種方法,這個類就被稱作一個描述符 def __get__(self, instance, owner): pass def __set__(self, instance, value): pass def __delete__(self, instance): pass
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‘ f1.name del f1.name #疑問:何時,何地,會觸發這三個方法的執行
#描述符Str class Str: def __get__(self, instance, owner): print(‘Str調用‘) def __set__(self, instance, value): print(‘Str設置...‘) def __delete__(self, instance): print(‘Str刪除...‘) #描述符Int class Int: def __get__(self, instance, owner): print(‘Int調用‘) def __set__(self, instance, value): print(‘Int設置...‘) def __delete__(self, instance): print(‘Int刪除...‘) class People: name=Str() age=Int() def __init__(self,name,age): #name被Str類代理,age被Int類代理, self.name=name self.age=age #何地?:定義成另外一個類的類屬性 #何時?:且看下列演示 p1=People(‘alex‘,18) #描述符Str的使用 p1.name p1.name=‘egon‘ del p1.name #描述符Int的使用 p1.age p1.age=18 del p1.age #我們來瞅瞅到底發生了什麽 print(p1.__dict__) print(People.__dict__) #補充 print(type(p1) == People) #type(obj)其實是查看obj是由哪個類實例化來的 print(type(p1).__dict__ == People.__dict__)
3、描述符分為兩種:
數據描述符:至少實現了__get__()和__set__()
class Foo: def __set__(self, instance, value): print(‘set‘) def __get__(self, instance, owner): print(‘get‘)
非數據描述符:沒有實現__set__()
class Foo: def __get__(self, instance, owner): print(‘get‘)
4、註意事項
# 描述符本身應該定義成新式類,被代理的類也應該是新式類 # 必須把描述符定義成這個類的類屬性,不能為定義到構造函數中 # 要嚴格遵循該優先級,優先級由高到底分別是 #1.類屬性 #2.數據描述符 #3.實例屬性 #4.非數據描述符 #5.找不到的屬性觸發__getattr__()
#描述符Str class Str: def __get__(self, instance, owner): print(‘Str調用‘) def __set__(self, instance, value): print(‘Str設置...‘) def __delete__(self, instance): print(‘Str刪除...‘) class People: name=Str() def __init__(self,name,age): #name被Str類代理,age被Int類代理, self.name=name self.age=age #基於上面的演示,我們已經知道,在一個類中定義描述符它就是一個類屬性,存在於類的屬性字典中,而不是實例的屬性字典 #那既然描述符被定義成了一個類屬性,直接通過類名也一定可以調用吧,沒錯 People.name #恩,調用類屬性name,本質就是在調用描述符Str,觸發了__get__() People.name=‘egon‘ #那賦值呢,我去,並沒有觸發__set__() del People.name #趕緊試試del,我去,也沒有觸發__delete__() #結論:描述符對類沒有作用-------->傻逼到家的結論 ‘‘‘ 原因:描述符在使用時被定義成另外一個類的類屬性,因而類屬性比二次加工的描述符偽裝而來的類屬性有更高的優先級 People.name #恩,調用類屬性name,找不到就去找描述符偽裝的類屬性name,觸發了__get__() People.name=‘egon‘ #那賦值呢,直接賦值了一個類屬性,它擁有更高的優先級,相當於覆蓋了描述符,肯定不會觸發描述符的__set__() del People.name #同上 ‘‘‘類屬性>數據描述符
#描述符Str class Str: def __get__(self, instance, owner): print(‘Str調用‘) def __set__(self, instance, value): print(‘Str設置...‘) def __delete__(self, instance): print(‘Str刪除...‘) class People: name=Str() def __init__(self,name,age): #name被Str類代理,age被Int類代理, self.name=name self.age=age p1=People(‘egon‘,18) #如果描述符是一個數據描述符(即有__get__又有__set__),那麽p1.name的調用與賦值都是觸發描述符的操作,於p1本身無關了,相當於覆蓋了實例的屬性 p1.name=‘egonnnnnn‘ p1.name print(p1.__dict__)#實例的屬性字典中沒有name,因為name是一個數據描述符,優先級高於實例屬性,查看/賦值/刪除都是跟描述符有關,與實例無關了 del p1.name數據描述符>實例屬性
class Foo: def func(self): print(‘我胡漢三又回來了‘) f1=Foo() f1.func() #調用類的方法,也可以說是調用非數據描述符 #函數是一個非數據描述符對象(一切皆對象麽) print(dir(Foo.func)) print(hasattr(Foo.func,‘__set__‘)) print(hasattr(Foo.func,‘__get__‘)) print(hasattr(Foo.func,‘__delete__‘)) #有人可能會問,描述符不都是類麽,函數怎麽算也應該是一個對象啊,怎麽就是描述符了 #笨蛋哥,描述符是類沒問題,描述符在應用的時候不都是實例化成一個類屬性麽 #函數就是一個由非描述符類實例化得到的對象 #沒錯,字符串也一樣 f1.func=‘這是實例屬性啊‘ print(f1.func) del f1.func #刪掉了非數據 f1.func()實例屬性>非數據屬性
class Foo: def func(self): print(‘我胡漢三又回來了‘) def __getattr__(self, item): print(‘找不到了當然是來找我啦‘,item) f1=Foo() f1.xxxxxxxxxxx非數據描述符>找不到
5.描述符的使用
class Str: def __init__(self,name): self.name=name def __get__(self, instance, owner): print(‘get--->‘,instance,owner) return instance.__dict__[self.name] def __set__(self, instance, value): print(‘set--->‘,instance,value) instance.__dict__[self.name]=value def __delete__(self, instance): print(‘delete--->‘,instance) instance.__dict__.pop(self.name) class People: name=Str(‘name‘) def __init__(self,name,age,salary): self.name=name self.age=age self.salary=salary p1=People(‘egon‘,18,3231.3) #調用 print(p1.__dict__) p1.name #賦值 print(p1.__dict__) p1.name=‘egonlin‘ print(p1.__dict__) #刪除 print(p1.__dict__) del p1.name print(p1.__dict__)version1
class Str: def __init__(self,name): self.name=name def __get__(self, instance, owner): print(‘get--->‘,instance,owner) return instance.__dict__[self.name] def __set__(self, instance, value): print(‘set--->‘,instance,value) instance.__dict__[self.name]=value def __delete__(self, instance): print(‘delete--->‘,instance) instance.__dict__.pop(self.name) class People: name=Str(‘name‘) def __init__(self,name,age,salary): self.name=name self.age=age self.salary=salary #疑問:如果我用類名去操作屬性呢 People.name #報錯,錯誤的根源在於類去操作屬性時,會把None傳給instance #修訂__get__方法 class Str: def __init__(self,name): self.name=name def __get__(self, instance, owner): print(‘get--->‘,instance,owner) if instance is None: return self return instance.__dict__[self.name] def __set__(self, instance, value): print(‘set--->‘,instance,value) instance.__dict__[self.name]=value def __delete__(self, instance): print(‘delete--->‘,instance) instance.__dict__.pop(self.name) class People: name=Str(‘name‘) def __init__(self,name,age,salary): self.name=name self.age=age self.salary=salary print(People.name) #完美,解決version2
class Str: def __init__(self,name,expected_type): self.name=name self.expected_type=expected_type def __get__(self, instance, owner): print(‘get--->‘,instance,owner) if instance is None: return self return instance.__dict__[self.name] def __set__(self, instance, value): print(‘set--->‘,instance,value) if not isinstance(value,self.expected_type): #如果不是期望的類型,則拋出異常 raise TypeError(‘Expected %s‘ %str(self.expected_type)) instance.__dict__[self.name]=value def __delete__(self, instance): print(‘delete--->‘,instance) instance.__dict__.pop(self.name) class People: name=Str(‘name‘,str) #新增類型限制str def __init__(self,name,age,salary): self.name=name self.age=age self.salary=salary p1=People(123,18,3333.3)#傳入的name因不是字符串類型而拋出異常version3
class Typed: def __init__(self,name,expected_type): self.name=name self.expected_type=expected_type def __get__(self, instance, owner): print(‘get--->‘,instance,owner) if instance is None: return self return instance.__dict__[self.name] def __set__(self, instance, value): print(‘set--->‘,instance,value) if not isinstance(value,self.expected_type): raise TypeError(‘Expected %s‘ %str(self.expected_type)) instance.__dict__[self.name]=value def __delete__(self, instance): print(‘delete--->‘,instance) instance.__dict__.pop(self.name) class People: name=Typed(‘name‘,str) age=Typed(‘name‘,int) salary=Typed(‘name‘,float) def __init__(self,name,age,salary): self.name=name self.age=age self.salary=salary p1=People(123,18,3333.3) p1=People(‘egon‘,‘18‘,3333.3) p1=People(‘egon‘,18,3333)version4
雖然我們已然能實現功能了,但是問題是,如果我們的類有很多屬性,你仍然采用在定義一堆類屬性的方式去實現,這就顯示很low。
我們先來看看類裝飾如何實現。
無參類裝飾器:
def decorate(cls): print("類裝飾器開始") return cls @decorate class People: def __init__(self,name): self.name = name p1 = People("hello world")
有參類裝飾器:
def typeassert(**kwargs): def decorate(cls): print(‘類的裝飾器開始運行啦------>‘,kwargs) return cls return decorate # 有參:1.運行typeassert(...)返回結果是decorate,此時參數都傳給kwargs 2.People=decorate(People) @typeassert(name=str, age=int, salary=float) class People: def __init__(self,name,age,salary): self.name=name self.age=age self.salary=salary p1=People(‘xiaohuar‘,18,3333.3)
class Typed: def __init__(self,name,expected_type): self.name=name self.expected_type=expected_type def __get__(self, instance, owner): print(‘get--->‘,instance,owner) if instance is None: return self return instance.__dict__[self.name] def __set__(self, instance, value): print(‘set--->‘,instance,value) if not isinstance(value,self.expected_type): raise TypeError(‘Expected %s‘ %str(self.expected_type)) instance.__dict__[self.name]=value def __delete__(self, instance): print(‘delete--->‘,instance) instance.__dict__.pop(self.name) def typeassert(**kwargs): def decorate(cls): print(‘類的裝飾器開始運行啦------>‘,kwargs) for name,expected_type in kwargs.items(): setattr(cls,name,Typed(name,expected_type)) 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 print(People.__dict__) p1=People(‘egon‘,18,3333.3)View Code
描述符總結:描述符是可以實現大部分python類特性中的底層魔法,包括@classmethod,@staticmethd,@property甚至是__slots__屬性。描述符是很多高級庫和框架的重要工具之一,描述符通常是使用到裝飾器或者元類的大型框架中的一個組件.
二、實現property
利用描述符原理完成一個自定制@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(‘alex‘,1,1) print(r1.area)
class Lazyproperty: def __init__(self,func): self.func=func def __get__(self, instance, owner): print(‘這是我們自己定制的靜態屬性,r1.area實際是要執行r1.area()‘) if instance is None: return self return self.func(instance) #此時你應該明白,到底是誰在為你做自動傳遞self的事情 class Room: def __init__(self,name,width,length): self.name=name self.width=width self.length=length @Lazyproperty #area=Lazyproperty(area) 相當於定義了一個類屬性,即描述符 def area(self): return self.width * self.length r1=Room(‘alex‘,1,1) print(r1.area)
三、實現classmethod
class ClassMethod: def __init__(self,func): self.func=func def __get__(self, instance, owner): #類來調用,instance為None,owner為類本身,實例來調用,instance為實例,owner為類本身, def feedback(): print(‘在這裏可以加功能啊...‘) return self.func(owner) return feedback class People: name=‘hi world‘ @ClassMethod # say_hi=ClassMethod(say_hi) def say_hi(cls): print(‘你好啊,帥哥 %s‘ %cls.name) People.say_hi() p1=People() p1.say_hi() #疑問,類方法如果有參數呢,好說,好說 class ClassMethod: def __init__(self,func): self.func=func def __get__(self, instance, owner): #類來調用,instance為None,owner為類本身,實例來調用,instance為實例,owner為類本身, def feedback(*args,**kwargs): print(‘在這裏可以加功能啊...‘) return self.func(owner,*args,**kwargs) return feedback class People: name=‘hi,world‘ @ClassMethod # say_hi=ClassMethod(say_hi) def say_hi(cls,msg): print(‘你好啊,帥哥 %s %s‘ %(cls.name,msg)) People.say_hi(‘你是那偷心的賊‘) p1=People() p1.say_hi(‘你是那偷心的賊‘)
四、實現StaticMethod
class StaticMethod: def __init__(self,func): self.func=func def __get__(self, instance, owner): #類來調用,instance為None,owner為類本身,實例來調用,instance為實例,owner為類本身, def feedback(*args,**kwargs): print(‘在這裏可以加功能啊...‘) return self.func(*args,**kwargs) return feedback class People: @StaticMethod# say_hi=StaticMethod(say_hi) def say_hi(x,y,z): print(‘------>‘,x,y,z) People.say_hi(1,2,3) p1=People() p1.say_hi(4,5,6)
五、再看property
一個靜態屬性property本質就是實現了get,set,delete三種方法
class Foo: @property def AAA(self): print(‘get的時候運行我啊‘) @AAA.setter def AAA(self,value): print(‘set的時候運行我啊‘) @AAA.deleter def AAA(self): print(‘delete的時候運行我啊‘) # 只有在屬性AAA定義property後才能定義AAA.setter,AAA.deleter f1 = Foo() f1.AAA f1.AAA = ‘aaa‘ del f1.AAA
class Foo: def get_AAA(self): print(‘get的時候運行我啊‘) def set_AAA(self,value): print(‘set的時候運行我啊‘) def delete_AAA(self): print(‘delete的時候運行我啊‘) AAA = property(get_AAA, set_AAA, delete_AAA) # 內置property三個參數與get,set,delete一一對應 f1 = Foo() f1.AAA # 觸發get f1.AAA = ‘aaa‘ # 觸發set del f1.AAA # 觸發del
python面向對象魔術方法補充