文章目錄
- Python中類的魔術方法之--反射
- 概述
- 與反射相關的内建函數
- 反射相關的魔術方法
- `__getattr__(self,item)`方法
- `__setattr__(self,item)`方法
- `__delattr__(self,item)`方法
- `__getattribute__(self,item)`方法
- 附加:通路流程圖
Python中類的魔術方法之–反射
概述
運作時,差別于編譯時,指的是程式被加載到記憶體中執行的時候。
反射,reflection,指的是運作時擷取類型定義資訊。
一個對象能夠在運作時,像照鏡子一樣,反射出其類型資訊。
簡單說,在Python中,能夠通過一個對象,找出其type、class、attribute或method的能力,稱為反射或者自 省。
具有反射能力的函數有 type()、isinstance()、callable()、dir()、getattr()等
示例:為類動态添加屬性:
class Point:
def __init__(self,x,y):
self.x = x
self.y = y
def __str__(self):
return "Point({},{})".format(self.x,self.y)
__repr__ = __str__
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.__dict__["a"] = 10 #為對象動态添加屬性值
print(p.__dict__)
p.z = 10 #為對象動态增加屬性
print(p.__dict__)
print("dir檢視p的屬性",dir(p))
print("******************************")
print(dir(Point))
# Point.__dict__["h"] = 100 #報錯出現TypeError異常。type(Point.__dict__) = mappingproxy
setattr(Point,"h",100) #動态為類對象添加屬性
Point.hh = 100 #動态為類對象添加屬性
print(dir(Point))
print("dir檢視p的屬性",dir(p))
print(type(Point.__dict__))

上例通過屬性字典__dict__來通路對象的屬性,本質上也是利用的反射的能力。
但是,上面的例子中,通路的方式不優雅,Python提供了内置的函數。
與反射相關的内建函數
内建函數 | 意義 |
getattr(object,name[,default]) | 通過name傳回object的屬性值。當屬性不存在,将使用default傳回,如果沒有default,則會抛出AttributeError異常。name必須為字元串 |
setattr(object,name,value) | object的屬性存在,則覆寫,不存在,新增 |
hasattr(object,name) | 判斷對象是否有這個名字的屬性,name必須為字元串 |
- 簡單示範:
class Point:
def __init__(self,x,y):
self.x = x
self.y = y
def __str__(self):
return "Point({},{})".format(self.x,self.y)
__repr__ = __str__
def show(self):
print("show 方法列印【{},{}】".format(self.x,self.y))
p1 = Point(4,5)
p2 = Point(5,6)
print(p1,p2,sep="\t")
print(p1.__dict__)
#添加屬性
setattr(p1,"z",10)
setattr(p2,"z",20)
print(getattr(p1,"__dict__"),p2.__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))
p3 = p1 + p2 #等價于 p3 = p1.__add__(p2) 綁定
print(p3.__add__) #b綁定
print(p3)
#動态為執行個體增加方法 未綁定
if not hasattr(p1,"__sub__"):
setattr(p1,"__sub__",lambda self,other:Point(self.x - other.x,self.y - other.y))
print(p1.__sub__(p2,p1))
print(p1.__sub__)
#__sub__在執行個體裡面
print(p1.__dict__)
print("- "*30)
#__add__在類裡面
print(Point.__dict__)
- 動态增加屬性的方式和裝飾器修飾一個類、Mixin方式的差異:
- 動态增加屬性的方式是運作時改變類或者示例的方式。
- 裝飾器或mixin都是定義時就決定了。
- 總結:反射能力具有更大的靈活性
反射相關的魔術方法
方法 | 對應的内建函數 | 意義 |
| 當通過搜尋執行個體、執行個體的類及祖先類查不到屬性,就會調用此方法 | |
| | 通過,通路執行個體屬性,進行增加、修改都要調用它 |
| | 當通過執行個體來删除屬性時調用此方法 |
| | 執行個體所有的屬性調用都從這個方法開始 |
- 屬性查找順序
-
-->執行個體調用__getattribute__()
-->instance.__dict__
--> 繼承的祖先類(直到 object)的instance.__class__.__dict__
--> 調用__dict__
__getattr__()
__getattr__(self,item)
方法
__getattr__(self,item)
- 執行個體屬性會按照繼承關系找,如果找不到,就會執行
方法,如果沒有這個方法,就會抛出 AttributeError異常表示找不到屬性__getattr__(self,item)
class Base:
n = 0
class Point(Base):
z = 6
def __init__(self,x,y):
self.x = x
self.y = y
def show(self):
print(self.x,self.y)
def __getattr__(self,item):
return "missing {}".format(item)
p = Point(5,6)
print(p.x,p.y,p.z,p.n)
print(p.t,p.h,p.tthh)
__setattr__(self,item)
方法
__setattr__(self,item)
- 執行個體通過.點号設定屬性,例如
屬性指派,就會調用self.x = x
,屬性要加到執行個體的__setattr__(self,item)
中,就需要自己完成。__dict__
-
方法,可以攔截對執行個體屬性的增加、修改操作,如果要設定生效,需要自己操作執行個體的__setattr__(self,item)
。__dict__
class Base:
n = 0
class Point(Base):
z = 6
def __init__(self,x,y):
self.x = x
self.y = y
def show(self):
print(self.x,self.y)
def __getattr__(self,item):
return "missing {}".format(item)
def __setattr__(self,key,value):
print("setattr {}={}".format(key,value))
p = Point(5,6)
p.h = 5
print(p.__dict__)
print(p.x) #可以看出示例自己的屬性self.x也會調用__setattr__方法
-
與__setattr__(self,item)
綜合示例:__getattr__(self,item)
class B:
b = 200
class A(B):
z = 100
d = {}
def __init__(self,x,y):
self.x = x #會調用__setattr__方法指派
setattr(self,"y",y) #會調用__setattr__方法指派
self.__dict__["a"] = 5 #直接寫入屬性字典
def __getattr__(self,item):
print(" -----get---------",item)
return self.d[item]
def __setattr__(self,key,value):
print("--------set-----",key,value)
self.d[key] = value
def __delattr__(self,item):
print("{} 被删除".format(item))
a = A(4,5)
print(a.__dict__) #可以看到a的屬性裡面沒有 x,y
print(A.__dict__) #屬性x,y被記錄在了類屬性d的字典裡
print(a.x,a.y) #找不到屬性會調用getattr ,依然可以擷取對應的x,y屬性
print(a.a)
__delattr__(self,item)
方法
__delattr__(self,item)
- 可以阻止通過執行個體來删除屬性的操作。但是通過類依然可以删除屬性。
- 當通過執行個體來删除屬性時調用此方法
class Point:
z = 5
def __init__(self,x,y):
self.x = x
self.y = y
def __delattr__(self,item):
print("{} 要被删除,但是沒做".format(item))
# self.__dict__.pop(item) #删除可以從字典中移除
p = Point(3,4)
print(p.__dict__)
del p.x
print(p.__dict__) #可以看到并沒有被删除
del p.z #通過執行個體通路删除屬性z,被阻止
print(Point.__dict__)
del Point.z #通過類通路删除屬性z可以
print(Point.__dict__)
__getattribute__(self,item)
方法
__getattribute__(self,item)
- 執行個體的所有的屬性通路,第一個都會調用
方法,它阻止了屬性的查找,該方法應該傳回(計算 後的)值或者抛出一個AttributeError異常。__getattribute__
- 它的return值将作為屬性查找的結果。
- 如果抛出AttributeError異常,則會直接調用
方法,因為表示屬性沒有找到。__getattr__
-
方法中為了避免在該方法中無限的遞歸,它的實作應該永遠調用基類的同名方法以通路需要的 任何屬性,例如__getattribute__
。object.__getattribute__(self, name)
class Base:
n = 0
class Point(Base):
z = 6
def __init__(self,x,y):
self.x = x
self.y = y
def __getattr__(self,item):
return "missing {}".format(item)
def __getattribute__(self,item):
# raise AttributeError("Not Found") #如果傳回AttributeError異常,會調用getattr方法
# pass
# return self.__dict__[item]
# return object.__getattribute__(self,item) #調用父類的方法
print("__getattribute__被調用 {}".format(item))
# return super(type(self),self).__getattribute__(item)
return super().__getattribute__(item)
p = Point(4,5)
print(p.__dict__)
print(p.x)
print(p.z)
附加:通路流程圖
- getattr屬性通路流程圖