chapter9.4、魔術方法反射
反射
概述
執行時,區別於編譯時,指的是程式被載入到記憶體中執行時
反射,reflection,指的是執行時獲取型別定義資訊
大部分動態語言提供了反射
一個物件執行時,像照鏡子一樣,反射出型別資訊
自省也是反射的一種稱呼
在Python中能夠通過一個物件,找出其 type,class ,attrbute或method的能力,稱為反射或者自省。
有反射能力的函式有:
type() 返回類,相當於.__class__
isinstance(物件,型別) 返回bool值,判斷型別,型別可以是多個型別包起來的元組,判斷其中是否有物件的型別
callable() 看一個物件是否為可呼叫物件,調__call__方法
dir() 返回類的或者物件的所有成員的列表,呼叫__dir__方法
class Point: def __init__(self,x,y): self.x = x self.y = y def __str__(self): return "Point({},{})".format(self.x, self.y) def show(self): print(self.x, self.y) p = Point(4, 5) print(p) print(p.__dict__) p.__dict__['y'] = 16 ###不推薦,沒事少改字典, print(p.__dict__) p.z = 10 print(p.__dict__) print(sorted(dir(p))) print(sorted(p.__dir__()))
屬性字典__dict__來訪問物件的屬性,本質上也是利用反射的能力,但這種方式十分不優雅
class Point: def __init__(self,x,y): self.x = x self.y = y def __str__(self): return "Point({},{})".format(self.x,self.y) def show(self): print(self.x,self.y) p1 = Point(4,5) p2 = Point(10,10) print(getattr(p1,'__dict__')) ##動態方法呼叫 if hasattr(p1,'show'): getattr(p1,'show')() ##動態增加方法,為類增添方法 if not hasattr(Point,'add'): setattr(Point,'add',lambda self,other:Point(self.x+other.x,self.y+other.y)) print(Point.add) print(p1.add) print(p1.add(p2))##繫結 ##動態贈添方法,為例項增添方法,未繫結 if not hasattr(p1,'sub'): setattr(p1,'sub',lambda self, other: Point(self.x - other.x, self.y - other.y)) print(p1.sub(p1,p1)) print(p1.sub)
getattr(object, name[, default=None]) 通過name返回object的屬性值。當屬性不存在,使用default返回,如果沒有default就丟擲AttributeError,name要求必須是字串。
setattr(object,name,value) object屬性存在,則覆蓋,不存在,新增
hasattr(object,name) 返回bool,判斷物件是否有這個名字的屬性,name必須為字串
delattr 刪除
可以繫結在物件和類上
可以對任何物件直接增加屬性。使用內建函式或者 物件.屬性訪問
裝飾器和Mixin都是定義時就修改了類的屬性,而setattr能在執行時修改屬性,或者刪除屬性。
反射有更大的靈活性
命令分發器:通過名稱找對應函式執行
class Dispatcher: def __init__(self): pass def reg(self,name,fn): setattr(self,name,fn) def run(self): while True: cmd = input(">>>>").strip() if cmd == 'quit': break getattr(self, cmd, lambda :print('command not found'))() dis=Dispatcher() dis.reg('ls',lambda :print('ls function')) dis.run()
使用getattr方法找物件的屬性的方式,比自己維護一個字典來建立名稱和函式之間的關係的方式好多了
反射的相關魔術方法
__getattr__ 例項查詢屬性的順序,會按照自己的字典,類的字典,繼承的祖先類的字典,直到object的字典,找不到就找__getattr__的方法,沒有就拋AttributeError
getattr是查詢屬性沒找到後的最後一道防線,然後就是拋異常
__setattr__ 例項通過.點號設定,例如self.x= x ,就會呼叫__setattr__,屬性要加到例項的__dict__中,就需要自己完成。該方法可以攔截例項屬性的增加,修改操作,如果要設定生效,就需要自己操作例項的__dict__。
class Point: s = 100 d = {} def __init__(self,x,y): self.x = x##調setattr self.y = y self.__dict__['a'] = 5##不調setattr def __getattr__(self, item): return self.d[item] def __setattr__(self, key, value): print(key) print(value) self.d[key] = value##將屬性存放到類的屬性中,此處可以改成任何儲存位置 def __delattr__(self, item): print('cannot del {}'.format(item)) p1 = Point(3,2)##呼叫setattr,新增屬性 print(p1.__dict__)##字典裡有a的屬性 print(Point.__dict__) print(p1.x,p1.y)##呼叫getattr print(p1.a)##在例項的字典找到了,不去往後找了
__delattr__ 可以阻止通過例項刪除屬性的操作,但通過類依然可以刪除屬性。
class Point: z = 100 def __init__(self,x,y): self.x = x self.y = y def __delattr__(self, item): print('cannot del {}'.format(item)) p1 = Point(3,2) del p1.x p1.z = 15 del p1.z print(Point.__dict__) print(p1.__dict__) del Point.z ##通過類刪除屬性,類的屬性刪除成功 print(Point.__dict__)
__getattribute__ 例項的所有屬性訪問,都會先呼叫__getattribute__方法,它阻止了屬性的查詢,它將返回值,或者拋異常,AttributeError
它的返回值就是屬性查詢的結果,
如果丟擲異常,就會直接呼叫__getattr__方法,因為表示屬性沒找到。
class B: n =0 class Point(B): z = 100 def __init__(self,x,y): self.x = x self.y = y def __getattr__(self, item): return 'misssing {}'.format(item) def __getattribute__(self, item): return item ###給啥返回啥 p1 = Point(3,2) print(p1.__dict__)##返回__dict__ print(p1.x) print(p1.z) print(p1.n) print(p1.t)##物件訪問屬性,都去調__getattribute__ print(Point.__dict__)##返回類字典, print(Point.z)##類訪問屬性
__getattr__方法中為了避免該方法中無限遞迴,它的實現應該永遠呼叫基類的同名方法以訪問需要的任何屬性,例如object.__getattr__(self,name)
class B: n =0 class Point(B): z = 100 def __init__(self,x,y): self.x = x self.y = y def __getattr__(self, item): return 'misssing {}'.format(item) def __getattribute__(self, item): #raise AttributeError ##丟擲異常 ##pass ##只有這句返回None,返回值就是None ##return self.__dict__[item] ##遞迴呼叫 return object.__getattribute__(self,item) ##到object找__getattribute__方法 p1 = Point(3,2) print(p1.__dict__)##返回__dict__ print(p1.x) print(p1.z) print(p1.n) print(p1.t) print(Point.__dict__) print(Point.z)
總結:
__getattr__() 當通過搜尋例項、例項的類及祖先類查不到屬性,就會呼叫此方法
__setattr__() 通過訪問例項的屬性,進行增加修改,都會呼叫它
__delattr__() 當通過例項來刪除屬性時呼叫此方法
__getattribute__() 例項的所有屬性呼叫都從這個方法開始