如何从字符串中的代码加载模块?

44 投票
6 回答
36595 浏览
提问于 2025-04-16 14:01

我有一些代码,它是以字符串的形式存在的,我想把它变成一个模块,但又不想把它写到硬盘上。

当我尝试使用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 个回答

7

你可以简单地创建一个模块对象,然后把它放进sys.modules里,再把你的代码放进去。

大概可以这样做:

import sys
from types import ModuleType
mod = ModuleType('mymodule')
sys.modules['mymodule'] = mod
exec(mycode, mod.__dict__)
31

imp.new_module 从 Python 3.4 开始就不推荐使用了,但在 Python 3.9 里仍然可以用。

imp.new_moduleimportlib.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()
62

下面是如何在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

撰写回答