2014-04-09 52 views
3

Python3添加了一個__prepare__,以便您可以用類別聲明替換用於收集項目的字典類型(請參閱here。)使用__prepare__我可以設置類以允許多重定義相同的成員函數。關閉範圍內的捕獲函數

class MultiType(type): 

    @classmethod 
    def __prepare__(metacls, name, bases, **kwds): 
     return collections.defaultdict(list) 


class Foo(metaclass=MultiType): 
    def bar(self, x): 
     return x - 1 

    def bar(self, x): 
     return x 

在模塊級,我可以使用裝飾玩一些花招:

def capture(f): 
    module = importlib.import_module(f.__module__) 
    if not hasattr(module, 'my_cache'): 
     setattr(module, 'my_cache', []) 
    module.my_cache.append(f) 

    @functools.wraps(f) 
    def _wrapper(*args, **kwargs): 
     return (func(*args, **kwargs) for func in module.my_cache) 
    return _wrapper 

@capture 
def foo(x): 
    return x - 1 

@capture 
def foo(x): 
    return 42 

但是,如果我這樣做一個閉包內,我可能會添加一些東西作用域在模塊級即應不是。

def foo(x): 
    @some_awesome_deco 
    def bar(y): 
     return y 

    @some_awesome_deco 
    def bar(y): 
     return 24 

    return bar(x+1) 

有沒有一種方法來識別和捕獲閉包的範圍內聲明的,這樣我可以比在模塊範圍內聲明的函數不同的方式處理這些功能,而無需引用關閉切換功能(即@some_awesome_deco(foo)?)

回答

3

如果您只需要支持CPython,那麼您的裝飾器就可以查看sys._getframe(1)框架對象,它表示執行裝飾器的代碼的執行框架。如果frame.f_locals字典與frame.f_globals字典具有相同的對象,則處於模塊級別。如果不是,則處於嵌套範圍內。

但是您必須生成某種範圍鍵;你可能離開存儲f_locals(這實際上不會影響實際當地人)的東西。請記住,當一個函數退出時,局部變量(以及幀)將被清除。我會返回一個特殊的可調用的,一個可變的,所以你可以在隨後的裝飾器調用中引用它。例如,您可以使用frame.f_locals[decorated_function.__name__]檢索該對象。

查看inspect module documenation,瞭解您可以期望在框架對象上找到哪些屬性。

演示:

>>> import sys 
>>> def nested_scope_detector(func): 
...  frame = sys._getframe(1) 
...  nested_scope = frame.f_globals is not frame.f_locals 
...  redefinition = func.__name__ in frame.f_locals 
...  if nested_scope: print('{!r} is located in a nested scope.'.format(func)) 
...  if redefinition: print('Redefining {!r}, previously bound to {!r}'.format(
...   func.__name__, frame.f_locals[func.__name__])) 
...  return func 
... 
>>> @nested_scope_detector 
... def foo(): pass 
... 
>>> @nested_scope_detector 
... def foo(): pass 
... 
Redefining 'foo', previously bound to <function foo at 0x10e931d08> 
>>> def bar(): 
...  @nested_scope_detector 
...  def foo(): pass 
...  @nested_scope_detector 
...  def foo(): pass 
... 
>>> bar() 
<function bar.<locals>.foo at 0x10eb4ef28> is located in a nested scope. 
<function bar.<locals>.foo at 0x10eb4eea0> is located in a nested scope. 
Redefining 'foo', previously bound to <function bar.<locals>.foo at 0x10eb4ef28> 

因此,你可以使用在返回的包裝函數的功能屬性來存儲你的功能:

def capture(f): 
    locals = sys._getframe(1).f_locals 
    preexisting = locals.get(f.__name__) 
    if preexisting is not None and hasattr(preexisting, 'functions'): 
     preexisting.functions.append(f) 
     return preexisting 

    @functools.wraps(f) 
    def _wrapper(*args, **kwargs): 
     return (func(*args, **kwargs) for func in _wrapper.functions) 
    _wrapper.functions = [f] 
    return _wrapper 

,它會在任何範圍內工作:

>>> @capture 
... def foo(): return 'bar' 
... 
>>> @capture 
... def foo(): return 'baz' 
... 
>>> foo() 
<generator object <genexpr> at 0x10eb45ee8> 
>>> list(foo()) 
['bar', 'baz'] 
>>> def bar(): 
...  @capture 
...  def foo(): return 'bar' 
...  @capture 
...  def foo(): return 'baz' 
...  return foo 
... 
>>> list(bar()()) 
['bar', 'baz'] 
+0

這會發生在導入時或始終在運行時?我沒有使用'sys._getframe'函數。 – wheaties

+0

@wheaties:在嵌套的作用域中,總是在運行時執行該函數。全局範圍僅在導入時執行。 –

+0

@wheaties:裝飾器只是裝飾裝飾對象時執行的函數。 –