捕获闭包范围内的函数

时间:2014-04-09 13:25:19

标签: python python-3.x closures metaprogramming decorator

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)?)

1 个答案:

答案 0 :(得分:4)

如果您需要支持的只是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']