尽管使用解释器成功,但无法使用函数中的 exec 动态导入
Can't dynamically import using exec from function despite success using interpreter
我在使用函数导入一些东西时遇到了一些问题,尽管可以在解释器中这样做。
假设文件夹 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。怎么回事?
函数中局部变量的处理方式不同于全局变量和对象属性(它们都使用字典将名称映射到值)。
定义函数后,Python 编译器会检查其代码并记录使用了哪些局部变量名称,并为每个名称指定一个 "slot"。调用该函数时,槽指的是框架对象中的部分内存。局部变量赋值和查找按编号访问槽,而不是按名称。这使得局部变量查找明显快于全局变量和属性查找(因为索引插槽比执行字典查找快得多)。
当您尝试使用 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
我在使用函数导入一些东西时遇到了一些问题,尽管可以在解释器中这样做。
假设文件夹 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。怎么回事?
函数中局部变量的处理方式不同于全局变量和对象属性(它们都使用字典将名称映射到值)。
定义函数后,Python 编译器会检查其代码并记录使用了哪些局部变量名称,并为每个名称指定一个 "slot"。调用该函数时,槽指的是框架对象中的部分内存。局部变量赋值和查找按编号访问槽,而不是按名称。这使得局部变量查找明显快于全局变量和属性查找(因为索引插槽比执行字典查找快得多)。
当您尝试使用 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