我在使用函数导入某些内容时遇到了一些问题,尽管可以在解释器中执行此操作。
想象一下,文件夹A中有一个文件input.py,而该文件又与我的脚本位于同一目录中。在此文件中,我们定义变量' B'。
B = 5
当我进入解释器时,以下命令为我提供了正确的B
值>>> import sys
>>> sys.path.append('A')
>>> exec('from inputs import *')
>>> print(B)
但是,如果我将该代码移到单独的文件中,请说“test.py'”
import sys
def import_stuff(import_dir):
sys.path.append(import_dir)
exec('from inputs import *')
print(B)
然后从解释器中调用它:
>>> import test
>>> test.import_stuff('A')
我收到一个NameError,找不到B.发生了什么事?
答案 0 :(得分:0)
函数中的局部变量与全局变量和对象属性(它们都使用字典将名称映射到值)的处理方式不同。
定义函数时,Python编译器会检查其代码并记下使用的局部变量名称,并为每个变量名称指定一个“槽”。调用该函数时,插槽指的是帧对象中的部分内存。局部变量赋值和查找按编号访问插槽,而不是按名称访问。这使得局部变量查找明显快于全局变量和属性查找(因为索引槽比执行dict查找要快得多)。
当您尝试使用exec
创建局部变量时,它会绕过插槽。编译器不知道将在exec
'代码中创建哪些变量,因此没有为它们分配插槽。这也是Python 3不允许在函数内部使用from module import *
的原因:在函数的编译时不知道要导入的名称(仅在运行它并导入导入的模块时)编译器无法为名称设置插槽。
即使您在exec
代码中单独初始化了您希望分配给的名称的局部变量,它仍然无效。 exec
'代码不知道它是从函数中运行的,并且总是想要将变量写入字典(永远不要使用插槽)。函数确实有一个本地命名空间字典,它可以捕获赋值(它们不会成为全局变量),但是通过locals()
函数使用它是非常不稳定的。每次调用locals()
时,存储在插槽中的所有局部变量的值都会被复制到字典中,但是不会发生另一个方向的复制(修改从locals()
返回的字典不起作用存储在插槽中并以正常方式访问的变量值。)
这让我想到了解决这个问题的最佳方法。而不是让exec
修改当前的命名空间(如果你在一个函数中,它会以一种不稳定的方式发生),你应该显式传入一个字典,以便用作它的命名空间。
def import_stuff(import_dir):
sys.path.append(import_dir)
namespace = {} # create a namespace dict
exec('from inputs import *', namespace) # pass it to exec
print(namespace["B"]) # read the results from the namespace