2012-01-09 201 views
12

我正在寫一個裝飾器,並且出於各種令人討厭的原因[0],檢查它正在包裝的函數是單獨定義還是作爲類的一部分來定義(以及哪些類是新類是子類)。Python:裝飾器可以確定一個函數是否正在類中定義?

例如:

def my_decorator(f): 
    defined_in_class = ?? 
    print "%r: %s" %(f, defined_in_class) 

@my_decorator 
def foo(): pass 

class Bar(object): 
    @my_decorator 
    def bar(self): pass 

應打印:

<function foo …>: False 
<function bar …>: True 

另外,請注意:

  • 在點裝飾應用的功能將仍然是一個函數,而不是一個未綁定的方法,因此測試實例/未綁定方法(使用typeofinspect)將不會工作。
  • 請只提供該解決這個問題的建議 - 我知道,有很多類似的方法來達到這個目的(例如,使用一個類裝飾),但我想他們在裝飾時有發生,不晚了。

[0]:具體來說,我在寫一個裝飾器,它可以很容易地用nose進行參數化測試。但是,nose而不是unittest.TestCase的子類上運行測試生成器,所以我希望我的修飾器能夠確定它是否在TestCase的子類中使用,並失敗並出現適當的錯誤。顯而易見的解決方案 - 使用isinstance(self, TestCase)調用包裝的函數之前是不行的,因爲包裝的函數需要是一個發生器,它沒有得到根本執行

+0

爲了好奇,這裏是結果:http://paste.pocoo.org/show/532430/ – 2012-01-09 20:23:52

回答

11

看看的inspect.stack()輸出時,你包的方法。當你的裝飾器的執行正在進行時,當前的堆棧框架是你的裝飾器的函數調用;下一個堆棧幀是正在應用於新方法的包裝操作@;而第三個框架本身就是類定義,因爲類定義是它自己的名稱空間(當它完成執行時被包裝以創建一個類),所以它應該有一個單獨的堆棧框架。

我建議,因此:

defined_in_class = (len(frames) > 2 and 
        frames[2][4][0].strip().startswith('class ')) 

如果所有那些瘋狂的指標看不可維護的,那麼你可以通過一塊以幀間隔件,這樣更明確:

import inspect 
frames = inspect.stack() 
defined_in_class = False 
if len(frames) > 2: 
    maybe_class_frame = frames[2] 
    statement_list = maybe_class_frame[4] 
    first_statment = statement_list[0] 
    if first_statment.strip().startswith('class '): 
     defined_in_class = True 

請注意,我做而不是看到任何方式來問你的包裝程序運行時Python的類名稱或繼承層次結構;這一點在處理步驟中「太早」,因爲類創建尚未完成。要麼自己解析以class開頭的行,然後查看該框架的全局變量以查找超類,要麼圍繞frames[1]代碼對象戳,以查看您可以學習的內容 - 看起來類名稱在上面的代碼中爲frames[1][0].f_code.co_name ,但我無法找到任何方式來了解課程創建完成後將要附加的超類。

-2

我覺得inspect模塊中的功能會做你想要什麼,尤其是isfunctionismethod

>>> import inspect 
>>> def foo(): pass 
... 
>>> inspect.isfunction(foo) 
True 
>>> inspect.ismethod(foo) 
False 
>>> class C(object): 
...  def foo(self): 
...    pass 
... 
>>> inspect.isfunction(C.foo) 
False 
>>> inspect.ismethod(C.foo) 
True 
>>> inspect.isfunction(C().foo) 
False 
>>> inspect.ismethod(C().foo) 
True 

然後,您可以按照Types and Members table訪問結合或未結合的方法中的功能:

>>> C.foo.im_func 
<function foo at 0x1062dfaa0> 
>>> inspect.isfunction(C.foo.im_func) 
True 
>>> inspect.ismethod(C.foo.im_func) 
False 
+0

我期望'inspect'會被涉及,但不是你描述的方式。看到我的第一個註釋(在應用裝飾器時,函數只是'function'的一個實例,而不是'unboundmethod'或'boundmethod')。 – 2012-01-09 18:37:52

+0

他沒有明確說明,但他的觀點是這不適用於課堂定義時間。 – 2012-01-09 18:38:03

+0

是的,你是對的 - 我已經更新了我的問題,以便更明確。我很感謝徹底的回答,但它只是沒有解決我的問題。 – 2012-01-09 18:41:00

2

我有一些hacky的解決方案:

import inspect 

def my_decorator(f): 
    args = inspect.getargspec(f).args 
    defined_in_class = bool(args and args[0] == 'self') 
    print "%r: %s" %(f, defined_in_class) 

但它在函數中存在self參數。

1

您可以檢查裝飾器本身是在模塊級調用還是嵌套在其他東西中。

defined_in_class = inspect.currentframe().f_back.f_code.co_name != "<module>" 
+0

Hrm ...但是這對嵌套在類內的類不起作用,會嗎? (例如,'def mk_class():class MyClass:...; return MyClass') – 2012-01-09 19:32:16

+0

函數執行或返回的內容不會影響'inspect.currentframe()。f_back'的值。唯一重要的是'my_decorator'被調用的地方,而且它總是與定義它的東西的層次相同。 – chepner 2012-01-09 22:04:13

+0

我可能誤解了你的評論;在調用封閉函數之前,其他函數中的任何裝飾函數都不會運行裝飾器,但是將會正確計算包裝函數的作用域。您可以在裝飾器中使用它來查看裝飾器運行時的封閉範圍列表:'inspect.stack()[1:]]'中的x [[x] .f_code.co_name]。 – chepner 2012-01-09 22:37:51

2

有點晚了這裏的聚會,但這一事實證明,如果被在一個類中定義的函數使用的裝飾是確定的可靠手段:

frames = inspect.stack() 

className = None 
for frame in frames[1:]: 
    if frame[3] == "<module>": 
     # At module level, go no further 
     break 
    elif '__module__' in frame[0].f_code.co_names: 
     className = frame[0].f_code.co_name 
     break 

的優勢這種方法在接受的答案是它可以與例如py2exe。

相關問題