1. 程式人生 > >chapter9.4、魔術方法反射

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__()  例項的所有屬性呼叫都從這個方法開始