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

cul*_*rón 8 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__)
Run Code Online (Sandbox Code Playgroud)

它打印这个:

 <function fn1.<locals>.fn2 at 0x7fd61bdf3ae8>
 <function fn1.<locals>.fn2 at 0x7fd61bdf3ae8>
 __main__
Run Code Online (Sandbox Code Playgroud)

正如我所看到的,Python看到函数是在局部空间中定义的(<locals>在打印文本中),但我看不出如何找到那些数据.我走过inspect模块,看不到任何类似的东西.

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

我该用什么?

Sha*_*ger 7

首先,直接的方法是检查函数的代码对象上是否设置了标志CO_NESTED:

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 ...
Run Code Online (Sandbox Code Playgroud)

也就是说,如果您关心的是您是否真正关闭了任何东西,还有另一种方法。不使用封闭作用域中任何内容的函数是嵌套的,但不是闭包,这种区别通常很重要。例如:

def foo(x):
    def bar(y):
        pass
    return bar
Run Code Online (Sandbox Code Playgroud)

没有创建闭包,因为没有bar使用调用范围内的变量foo。相比之下,即使它是一个垃圾引用,这也只是通过从封闭范围中读取 的值来创建一个闭包:x

def foo(x):
    def baz(y):
        x
    return baz
Run Code Online (Sandbox Code Playgroud)

您可以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)
Run Code Online (Sandbox Code Playgroud)


jua*_*aga 5

好吧,这是一个 hacky 方法:

'<locals>' in f.__qualname__
Run Code Online (Sandbox Code Playgroud)

但对我来说它似乎很脆弱。

另一种方法是使用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
Run Code Online (Sandbox Code Playgroud)