如何从字符串中的代码加载模块?
我有一些代码,它是以字符串的形式存在的,我想把它变成一个模块,但又不想把它写到硬盘上。
当我尝试使用imp和StringIO对象来实现这个时,我遇到了以下问题:
>>> imp.load_source('my_module', '', StringIO('print "hello world"'))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: load_source() argument 3 must be file, not instance
>>> imp.load_module('my_module', StringIO('print "hello world"'), '', ('', '', 0))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: load_module arg#2 should be a file or None
我该如何在没有实际文件的情况下创建这个模块呢?或者,有没有办法把StringIO包装成一个文件,而不写入硬盘?
更新:
注意:这个问题在python3中也是存在的。
我尝试加载的代码只有部分可信。我用ast工具检查过,确认它没有导入任何我不喜欢的东西,也没有做任何我不喜欢的事情,但我还是不太放心,因为在运行这个代码的时候,可能会有本地变量被修改,而且我也不太相信自己的代码能不干扰我想导入的代码。
我创建了一个空模块,里面只包含以下内容:
def load(code):
# Delete all local variables
globals()['code'] = code
del locals()['code']
# Run the code
exec(globals()['code'])
# Delete any global variables we've added
del globals()['load']
del globals()['code']
# Copy k so we can use it
if 'k' in locals():
globals()['k'] = locals()['k']
del locals()['k']
# Copy the rest of the variables
for k in locals().keys():
globals()[k] = locals()[k]
然后你可以导入mymodule
并调用mymodule.load(code)
。这样对我来说是可行的,因为我确保了我加载的代码没有使用globals
。而且,global
这个关键词只是一个解析指令,不能引用exec外部的任何东西。
其实,这样在不写入硬盘的情况下导入模块真的太麻烦了,但如果你真的想这么做,我觉得这是最好的方法。
6 个回答
你可以简单地创建一个模块对象,然后把它放进sys.modules里,再把你的代码放进去。
大概可以这样做:
import sys
from types import ModuleType
mod = ModuleType('mymodule')
sys.modules['mymodule'] = mod
exec(mycode, mod.__dict__)
imp.new_module
从 Python 3.4 开始就不推荐使用了,但在 Python 3.9 里仍然可以用。
imp.new_module
被 importlib.util.module_from_spec
替代了。
importlib.util.module_from_spec 比用
types.ModuleType
来创建新模块更受欢迎,因为它可以在模块上设置尽可能多的与导入相关的属性。importlib.util.spec_from_loader 使用可用的加载器 API,比如
InspectLoader.is_package()
,来填补 spec 中缺失的信息。
这些模块属性包括 __builtins__
、__doc__
、__loader__
、__name__
、__package__
和 __spec__
。
import sys, importlib.util
def import_module_from_string(name: str, source: str):
"""
Import module from source string.
Example use:
import_module_from_string("m", "f = lambda: print('hello')")
m.f()
"""
spec = importlib.util.spec_from_loader(name, loader=None)
module = importlib.util.module_from_spec(spec)
exec(source, module.__dict__)
sys.modules[name] = module
globals()[name] = module
# demo
# note: "if True:" allows to indent the source string
import_module_from_string('hello_module', '''if True:
def hello():
print('hello')
''')
hello_module.hello()
下面是如何在Python 2.x中将一个字符串作为模块导入的方法:
import sys,imp
my_code = 'a = 5'
mymodule = imp.new_module('mymodule')
exec my_code in mymodule.__dict__
在Python 3中,exec是一个函数,所以这样做应该可以:
import sys,imp
my_code = 'a = 5'
mymodule = imp.new_module('mymodule')
exec(my_code, mymodule.__dict__)
现在可以像这样访问模块的属性(还有函数、类等等):
print(mymodule.a)
>>> 5
如果想忽略下一次导入的尝试,可以把这个模块添加到sys
中:
sys.modules['mymodule'] = mymodule