2014-12-13 72 views
1

我嘗試創建一個跟蹤裝飾器,以跟蹤哪個方法有調用。 讓我們先閱讀代碼。代碼是用Python編寫的2.7python裝飾代理不工作

class Decor(object): 
    def __init__(self,cls): 
     self.cls=cls 

    def __call__(self,*arg): 
     self.instance = self.cls(*arg) 
     return self 

    def __getattr__(self,name): 
     print "attribute name of instance of class {} = {}".format(self.cls.__name__,name) 
     return getattr(self.instance,name) 

@Decor 
class Foo(object): 
    attr1 = 10 
    def __init__(self,value): 
     self.value = value 

    def method1(self): 
     self.method2() 
     print self 
     print "value={}".format(self.value) 

    def method2(self): 
     print "hi" 


f=Foo(10) 
print f 
f.method1() 

輸出:

1. <__main__.Decor object at 0x02758B90> 
2. attribute name of instance of class Foo = method1 
3. hi 
4. <__main__.Foo object at 0x02758C30> 
5. value=10 

我預計方法2將得到打印爲方法1。你能解釋爲什麼method2不能詳細打印嗎?

我不明白輸出line4。它顯示Foo對象而不是Decor對象。 f是一個Decor對象,爲什麼self是Foo對象?請解釋!

請給我一個解決這個問題的建議。謝謝

回答

2

這裏的問題是Foo.instance1裏面的selfFoo實例,而不是Decor實例。

你可以看到這一點,如果你要打印出f.method1本身:它是一個綁定的方法,其實例是f

>>> f.method1 
attribute name of instance of class Foo = method1 
<bound method Foo.method1 of <__main__.Foo object at 0x10c641e90>> 

爲什麼?那麼,你返回getattr(self.instance, 'method1'),它與self.instance.method1做同樣的事情,所以它的self.instance被燒入綁定的方法。

所以,當method1代碼查找self.method2(或self.value),它在尋找它的Foo實例,所以你的代碼沒有得到運行。

如果你不明白這一點,請嘗試閱讀How Methods Work。 (和末尾部分的Descriptor HowTo Guide的鏈接,如果你不希望只接受屬性查找是魔法。)


所以,如果你想self在裏面instance1一個Decor例如,你有用Decor實例返回綁定方法,而不是Foo實例。您可以使用types.MethodType手動創建一個。 (或者,一旦你理解的描述,你可以直接查找函數實現,然後手動調用它__get__。)

例如:

def __getattr__(self, name): 
    print "attribute name of instance of class {} = {}".format(self.cls.__name__,name) 
    attr = getattr(self.instance, name) 
    if callable(attr): 
     return types.MethodType(attr.__func__, self, type(self)) 
    else: 
     return attr 

(如果你想這與classmethods,staticmethods工作,像保存在背景字典等功能的非可調用的方法,它需要更多的複雜不僅僅是callable,當然)

現在:

>>> f.method1 
attribute name of instance of class Foo = method1 
Out[26]: <bound method Decor.method1 of <__main__.Decor object at 0x10c7e9f10>> 

所以,當我們把它叫做:

>>> f.method1() 
attribute name of instance of class Foo = method1 
attribute name of instance of class Foo = method2 
hi 
<__main__.Decor object at 0x10c7e9f10> 
attribute name of instance of class Foo = value 
value=10 
1

My other answer解釋爲什麼現有的代碼不能正常工作,以及如何解決它(雖然我不知道以及如何解釋它...)。

但是通常還有一種更簡單的方法可以做你想做的事情:你可以修改類而不是包裝它。當然,這意味着你必須使用__getattribute__代替__getattr__,與所有常見的注意事項不小心遞歸調用自己:*

def Decor(cls): 
    old_getattribute = getattr(cls, '__getattribute__') 
    def new_getattribute(self, name): 
     print "attribute name of instance of class {} = {}".format(cls.__name__, name) 
     return old_getattribute(self, name) 
    cls.__getattribute__ = new_getattribute 
    return cls 

這可能會打印出更多比你想要的,例如,在f = Foo(10),它將顯示正在查找的__class__屬性(but not __init__)。你當然可以過濾掉你不想記錄的名字。

這也有一些限制,包裝和委託沒有。它不適用於舊式課程;你不能用它來動態地包裝內建或C擴展類;等等。另一方面,這意味着Foo有自己的名稱,文檔字符串等,而不是Decor的,不需要通過functools.wraps-style複製它們。


*請注意,文檔說,你應該始終調用內__getattribute__基類的版本屬性訪問,但在這種情況下,你要調用cls的原始版本,這當然會成爲基地如果cls沒有定義一個版本的版本。