標籤:觀察 物件導向 ror 使用者空間 邏輯 [] 方法 tac 魔法
自省/反射
什麼是反射?
自省也稱作反射,這個性質展示了某對象是如何在運行期取得自身資訊的。
並且在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.ysetattr(x, ‘y‘, v) #x.y = v可以設定屬性setattr(b1,‘show_name‘,lambda self:self.name+‘sb‘)delattr(x, ‘y‘) #del x.yhasattr(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:‘我是描述資訊‘ passprint(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 Truewith 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物件導向進階:反射、魔法方法、元類