python面向對象高級:反射、魔法方法、元類
自省/反射
什麽是反射?
自省也稱作反射,這個性質展示了某對象是如何在運行期取得自身信息的。
並且在python裏,反射可以使得程序運行時對象擁有增刪改查它本身屬性或行為的一種能力
如果Python不支持某種形式的自省功能,dir和type內建函數,將很難正常工作。還有那些特殊屬性,像__dict__,__name__及__doc__
反射的使用場景?
即插即用,即可以事先定義好接口,接口只有在被完成後才會真正執行
比如:如果和別人共同合作開發項目,但是需要用到對方的類的方法,對方還沒完成
f1=FtpClient(‘192.168.1.1‘) if hasattr(f1,‘get‘): func_get=getattr(f1,‘get‘) func_get() else: print(‘---->不存在此方法‘) print(‘處理其他的邏輯‘)
四個可以實現自省的函數
python通過字符串的形式操作對象相關的屬性。python中的一切事物都是對象(都可以使用反射)
getattr(x, ‘y‘) #x.y setattr(x, ‘y‘, v) #x.y = v可以設置屬性setattr(b1,‘show_name‘,lambda self:self.name+‘sb‘) delattr(x, ‘y‘) #del x.y hasattr(b1,‘name‘)
魔法方法
__setattr__,__delattr__,__getattr__
必知
1)覆寫以上函數非常容易出現無限遞歸,因為無論是操縱全局自省函數,還是對象.屬性都是去調用對應的魔法方法。所以只能操縱魔法字典這個屬性
def __getattr__(self, item): return self.__dict__[item]
2)覆寫以上函數的格式和對應的全局自省函數參數相同,因為全局自省函數就是調用對應的它們
__getattribute__
__getattr__對比__getattribute__有什麽區別?
1)當使用 對象.屬性 找不到的時候,會調用getattr,返回一個值或AttributeError異常,但是若屬性存在,則不調用。
但是__getattribute__無論屬性存在與否都會調用
2)當__getattribute__與__getattr__同時存在,只會執行__getattrbute__,除非__getattribute__在執行過程中拋出異常AttributeError
描述符協議
__get__,__set__,__delete__ # 描述符
什麽是描述符?
描述符的功能就是描述其他類的類屬性
描述符本質就是一個新式類,在這個新式類中,至少實現了__get__(),__set__(),__delete__()中的一個,這也被稱為描述符協議
__get__():調用一個屬性時,觸發
__set__():為一個屬性賦值時,觸發
__delete__():采用del刪除屬性時,觸發
描述符的原理是將描述符定義為指定類的類屬性,產生指定操作時,便去屬性字典裏面找,如果是描述符對象,便執行
class Int: def __get__(self, instance, owner): #owner是instance的class print(‘Int調用‘) def __set__(self, instance, value): print(‘Int設置...‘) def __delete__(self, instance): print(‘Int刪除...‘) class People: age=Int() def __init__(self,name,age): self.age=age #執行Int.__get__
描述符的分類
描述符分兩種,數據描述符(至少實現類__get__()和__set__()), 非數據描述符(沒有實現__set__()),
考慮要定義哪一種,主要是考慮需不需要給實例定義自己相同的屬性(該屬性名與類描述符名相同)
並且數據描述符的優先級只有類屬性可以覆蓋。
4.__set__設置類.描述符時,新設的類屬性值會覆蓋描述符,但是通過實例.描述符卻不會覆蓋。在類的__dict__可以觀察到。
5.描述符只能通過 類.描述符名=XX 改變,實例是無法改變,並且實例可以設置描述符同名的屬性(所以數據描述符,和非數據描述符就是有區別,區別就是有__set__方法,就應該去執行,沒有的話就直接綁定了)
描述符的用處
幹預被描述的屬性的操作
當操縱指定的類屬性時,會跳到相應的描述符函數並執行返回。我們可以在描述符函數通過操作指定對象的__dict__模仿正常的屬性操作。所以說這中間多了個幹預工程,常用於制作類構造函數的類型限制。
給類加上類屬性的三種方法:(註意,函數也屬於類屬性)
1)直接在類中聲明,例如a=Des()
2)定義一個函數’裝飾器‘,裝飾於類中。在函數裏面為類添加屬性
3 ) 定義一個類’裝飾器‘,然後裝飾於另外一個類的方法裏,
然後被裝飾器的函數名就指向這個類’裝飾器‘產生的對象(obj=cls(func)),若此時類裝飾器又實現了__get__方法,
換句話說,函數是類屬性,類屬性指向描述符對象。專門應用於函數。
擴展:類裝飾器
類裝飾器,原理和函數裝飾器一樣,前者將函數當作對象傳入,後者將類當作對象傳入,通過類比可知。
當構造函數的參數過多時,可以額外寫一個函數作裝飾器(參數為 屬性名=期望類型..的可變參數**kwargs,函數裏面,為類加入描述符屬性(類.屬性名=描述符)
例如:自制proprety就是類裝飾器配合描述符的好例子
class Pro(): def __init__(self,ab): self.ab=ab def __get__(self,instance,owner): return self.ab(instance) class Apple(): def __init__(self,a,b): self.a=a self.b=b @Pro def ab(self): return self.a*self.b def __str__(self): return ("是我a=%s,b=%s"%(self.a,self.b)) if __name__==‘__main__‘: aa=Apple(2,3) print(aa.ab)
__setitem__,__getitem,__delitem__ #等於c++的運算符[]重載
__str__,__repr__,__format__
1)改變對象的字符串顯示__str__,__repr__,自定制格式化字符串__format__
2)在類中__str__和__repr__沒什麽區別。如果__str__沒有被定義,那麽就會使用__repr__來代替輸出
註意:這倆方法的返回值必須是字符串,否則拋出異常
__slots__ #內存優化工具,順便可以限制屬性
用法:
可以選擇性定義的一個類屬性,類型是列表,列表元素是定義的實例屬性名(字符串)
作用:
1)__slots__機制借由數組存儲屬性來節約內存。因為類的字典是共享的,而每個實例的是獨立的,字典占用內存很大,
2)在類中定義__slots__屬性列表後,實例通過一個很小的固定大小的數組來構建,在__slots__中列出的屬 性名在內部被映射到這個數組的指定小標上。而不是為每個實例定義一個字典,節約內存。
3)使用__slots__的代價就是不能再給實例添加新的屬性,只能使用在__slots__中定義的那些屬性名
__next__和__iter__ #實現叠代器協議
__doc__ #類中最開頭處的字符串 class Foo: ‘我是描述信息‘ pass print(Foo.__doc__)
__module__ #表示當前操作的對象在那個模塊 __class__ #表示當前操作的對象的類是什麽
__del__ #析構方法,當對象在內存中被釋放時,自動觸發執行。
1)創建數據庫類,用該類實例化出數據庫鏈接對象,對象本身是存放於用戶空間內存中,而鏈接則是由操作系統管理的,存放於內核空間內存中
當程序結束時,python只會回收自己的內存空間,即用戶態內存,而操作系統的資源則沒有被回收,這就需要我們定制__del__,在對象被刪除前向操作系統發起關閉數據庫鏈接的系統調用,回收資源
2)f=open(‘a.txt‘) #做了兩件事,在用戶空間拿到一個f變量,在操作系統內核空間打開一個文件
del f #只回收用戶空間的f,操作系統的文件還處於打開狀態
#所以我們應該在del f之前保證f.close()執行,即便是沒有del,程序執行完畢也會自動del清理資源。
__call__ #變為函數對象
__enter__和__exit__ #上下文管理器
註意的是:__exit__的函數參數有點多。還有一般這上下文管理器中的異常會有點多。
上下文中間的代碼發生異常時,若__exit__()返回值為True,那麽異常就會被吞掉,就好像啥都沒發生一樣,
例如:
class Open: def __init__(self,name): self.name=name def __enter__(self): print(‘出現with語句,對象的__enter__被觸發,有返回值則賦值給as聲明的變量‘) def __exit__(self, exc_type, exc_val, exc_tb): print(‘with中代碼塊執行完畢時執行我啊‘) print(exc_type) print(exc_val) print(exc_tb) #return True with Open(‘a.txt‘) as f: print(‘=====>執行代碼塊‘) raise AttributeError(‘***著火啦,救火啊***‘) print(‘0‘*100) #------------------------------->不會執行
元類
type()函數產生類
Foo=type(class_name,class_bases,class_dic)
第 1 個參數是字符串 ‘Foo’,表示類名
第 2 個參數是元組 (object, ),表示所有的父類
第 3 個參數是字典,這裏是一個空字典,表示沒有定義屬性和方法
使用自己定義的元類(class A(metaclass=MyType))
當定義自己的元類時,首先要繼承type(註意一下,元類中self意味著實例化的cls)
1)如果想幹涉‘元類-->類’,就改寫__new__。(def__new__(cls,class_name,class_bases,class_dict))
因為類創建對象的流程是__new__創建對象後將對象地址傳給__init__,__init__加工上自己所需的屬性,
換句話說,前者負責創建返回,後者負責加工。
2)如果想幹涉’對象的創建‘,就改寫元類的__call__,其實元類的__call__才是大佬,
由它決定要不要調用類的__new__函數和__init__函數。默認會調
python面向對象高級:反射、魔法方法、元類