天天看點

Python中類的魔術方法之--反射

文章目錄

  • ​​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__))      
Python中類的魔術方法之--反射

上例通過屬性字典__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__)      
Python中類的魔術方法之--反射
  • 動态增加屬性的方式和裝飾器修飾一個類、Mixin方式的差異:
  1. 動态增加屬性的方式是運作時改變類或者示例的方式。
  2. 裝飾器或mixin都是定義時就決定了。
  3. 總結:反射能力具有更大的靈活性

反射相關的魔術方法

方法 對應的内建函數 意義

​__getattr__(self,item)​

當通過搜尋執行個體、執行個體的類及祖先類查不到屬性,就會調用此方法

​__setattr_(slef,key,values)​

​setattr(object,name,value)​

通過,通路執行個體屬性,進行增加、修改都要調用它

​__delattr__(self,item)​

​del obj.k​

當通過執行個體來删除屬性時調用此方法

​__getattribute__()​

​getattr(object,name[,default])​

執行個體所有的屬性調用都從這個方法開始
  • 屬性查找順序
  1. ​執行個體調用__getattribute__()​

    ​​ -->​

    ​instance.__dict__​

    ​​ -->​

    ​instance.__class__.__dict__​

    ​​ --> 繼承的祖先類(直到 object)的​

    ​__dict__​

    ​​ --> 調用​

    ​__getattr__()​

​__getattr__(self,item)​

​方法

  • 執行個體屬性會按照繼承關系找,如果找不到,就會執行​

    ​__getattr__(self,item)​

    ​ 方法,如果沒有這個方法,就會抛出 AttributeError異常表示找不到屬性
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)      
Python中類的魔術方法之--反射

​__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__方法      
Python中類的魔術方法之--反射
  • ​__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)      
Python中類的魔術方法之--反射

​__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__)      
Python中類的魔術方法之--反射

​__getattribute__(self,item)​

​方法

  • 執行個體的所有的屬性通路,第一個都會調用​

    ​__getattribute__​

    ​ 方法,它阻止了屬性的查找,該方法應該傳回(計算 後的)值或者抛出一個AttributeError異常。
  1. 它的return值将作為屬性查找的結果。
  2. 如果抛出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屬性通路流程圖