如何检测函数是否已在本地定义?

时间:2017-09-28 23:36:08

标签: python python-3.x python-decorators

在Python中,我有一个装饰器,如果函数在调用它的本地定义,则必须跳过任何实际工作。我制作了一个简单的测试脚本:

def fn1():
    # @my_decorator will be here
    def fn2():
        pass

    print(fn2)
    return fn2

x = fn1()
print(x)
print(x.__module__)

打印出来:

 <function fn1.<locals>.fn2 at 0x7fd61bdf3ae8>
 <function fn1.<locals>.fn2 at 0x7fd61bdf3ae8>
 __main__

正如我所见,Python看到函数是在本地空间中定义的(打印文本中为<locals>),但我无法看到如何找到该位数据。我走过inspect模块,看不到任何类似的东西。

我不能依赖函数是否在全局变量中。

我该使用什么?

2 个答案:

答案 0 :(得分:4)

嗯,这是一个hacky方法:

'<locals>' in f.__qualname__
但是,对我来说似乎很脆弱。

另一种方法是使用Frame,但我更喜欢,我认为:

In [1]: import inspect

In [2]: def deco(f):
   ...:     try:
   ...:         frame = inspect.currentframe()
   ...:         print(frame.f_back.f_locals is globals())
   ...:     finally:
   ...:         del frame
   ...:     return f
   ...:

In [3]: @deco
   ...: def g(): pass
   ...:
True

In [4]: def f():
   ...:     @deco
   ...:     def g(): pass
   ...:

In [5]: f()
False

答案 1 :(得分:4)

首先,直接的方法是检查是否the CO_NESTED flag is set on the function's code object

import inspect

...

def is_nested(func):
    return func.__code__.co_flags & inspect.CO_NESTED

def deco(func):
    if is_nested(func):
        # This is a nested function, return it unchanged
        return func
    ... otherwise, do your decoration here ...

那就是说,如果你关心的是你是否真的关闭过任何东西,还有另一种方法。不使用封闭范围内任何内容的函数是嵌套的,但不是闭包,这种区别通常很重要。例如:

def foo(x):
    def bar(y):
        pass
    return bar

进行关闭,因为bar不使用foo调用范围内的变量。相比之下,即使它是一个垃圾引用,这个 只是通过从封闭范围中读取x的值来构成一个闭包:

def foo(x):
    def baz(y):
        x
    return baz

您可以通过测试bar属性(如果没有关闭嵌套变量,则为baz)或通过检查来确定__closure__None之间的区别co_freevars对象的__code__属性(这是一个名字关闭的元组,所以如果它是空的,那么它不是一个闭包,虽然它可能仍然是一个嵌套函数):

def is_closure(func):
    return func.__closure__ is not None
    # Or using documented names, since __closure__ isn't for some reason,
    # co_freevars is a tuple of names captured from nested scope
    return bool(func.__code__.co_freevars)

    # Or on 3.3+, you even get a function to aid you:
    return bool(inspect.getclosurevars(func).nonlocals)