python函数即使出现在locals()中也没有定义

时间:2018-06-13 13:29:36

标签: python

import inspect
from functools import wraps

def add_value_checks(*settings):
    def wrapping_function(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            code = inspect.getsource(func)
            hoax_code = code[code.find('\n')+1:code.find(':')+2]
            hoax_code += '\treturn inspect.currentframe()\n'
            if func.__name__ == 'run':
                hoax_code = hoax_code.replace('run', 'run_hoax', 1)
                print(hoax_code)
                exec(hoax_code)
                print(locals())
                frame = run_hoax(*args, **kwargs)
            targs, Args, Kwargs, values = inspect.getargvalues(frame)
            for i in targs:
                print("    %s = %s" % (i, values[i]))

        return wrapper
    return wrapping_function

@add_value_checks()
def run(a, b, c=True, d=5):
    print("Hello World")

if __name__ == '__main__':
    run(3,4)

我看到run_hoax()中存在函数locals(),但我仍然得到 NameError:name' run_hoax'未定义。可能的原因是什么?

这些简单的例子很好用:

In [1]: code = "def fun():\n\tprint(3)"

In [2]: exec(code)

In [3]: fun()
3

我无法理解这里的问题是什么。

1 个答案:

答案 0 :(得分:1)

当您将其放入函数中时,您发布的示例将停止工作:

In [4]: def f():
   ...:     code = "def foo():\n\treturn 1"
   ...:     exec(code)
   ...:     print(locals())
   ...:     return foo()
   ...: 

In [5]: f()
{'foo': <function foo at 0x7f780dcb48c8>, 'code': 'def foo():\n\treturn 1'}
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-5-0ec059b9bfe1> in <module>()
----> 1 f()

<ipython-input-4-fd87a1e83c39> in f()
      3     exec(code)
      4     print(locals())
----> 5     return foo()

NameError: name 'foo' is not defined

函数很特殊,因为它们引入了一个单独的局部作用域。编译函数时,该范围内的变量是固定的(变量的数量和名称,而不是它们的值)。您可以通过检查函数的.__code__.co_varnames属性:

来查看
In [7]: f.__code__.co_varnames
Out[7]: ('code',)

固定的变量名称注册表是从函数内部查找名称时使用的。当您拨打exec时,该注册表不会更新。

@Avezan显示的方法有效,因为它不使用局部变量 - 它使用locals,其中 由您的exec调用更新。

@Ishan Srivastava的解决方案有效,因为它在全局命名空间中执行您的代码,但没有在编译时修复的限制。

有关Python的名称查找机制以及localexec如何相互关联的详细说明,请参阅this blogpost