【问题标题】:How to detect if a function has been defined locally?如何检测函数是否已在本地定义?
【发布时间】:2018-03-10 19:45:07
【问题描述】:

在 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 看到该函数是在本地空间中定义的(打印文本中的&lt;locals&gt;),但我看不出如何找到那部分数据。我浏览了inspect 模块,没有看到任何类似的东西。

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

我用什么?

【问题讨论】:

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


    【解决方案1】:

    首先,直接的方法是检查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
    

    您可以通过测试__closure__ 属性(如果没有关闭嵌套变量,则为None)或检查@987654334 的co_freevars 属性来区分barbaz @ 对象(它是一个封闭的名称元组,所以如果它是空的,那么它不是一个闭包,尽管它可能仍然是一个嵌套函数):

    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)
    

    【讨论】:

      【解决方案2】:

      好吧,这是一个 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
      

      【讨论】:

      • 我认为这是一个合法的好解决方案。 __qualname__ 也是用于创建函数的 repr 的(至少在 CPython 中),它没有检查其他隐藏属性。
      • @JimFasarakisHilliard 是的,我在玩 inspect.currentframe().f_back.f_locals is globals(),这似乎也有效,但我更喜欢 less
      猜你喜欢
      • 1970-01-01
      • 2011-07-06
      • 2012-09-07
      • 2017-08-24
      • 2023-03-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-10
      相关资源
      最近更新 更多