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

新手上路,请多包涵

我有一些字符串形式的代码,想用它制作一个模块而不写入磁盘。

当我尝试使用 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 之外的任何内容。

对于 import 没有写入磁盘的模块来说,这确实是太多的工作,但如果你想这样做,我相信这是最好的方法。

原文由 Conley Owens 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 563
2 个回答

以下是将字符串作为模块导入的方法 ( 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

原文由 Remi 发布,翻译遵循 CC BY-SA 3.0 许可协议

imp.new_module 自 python 3.4 以来已 弃用,但它仍然适用于 python 3.9

imp.new_module 被替换为 importlib.util.module_from_spec

importlib.util.module_from_spec 优于使用 types.ModuleType 创建新模块,因为 spec 用于在模块上设置尽可能多的导入控制 属性

importlib.util.spec_from_loader 使用可用的加载器 API(例如 InspectLoader.is_package() )来填充规范中任何缺失的信息。

these module attributes are __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()

原文由 milahu 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题