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

时间:2011-03-19 14:43:35

标签: python

我有一些字符串形式的代码,并希望在不写入磁盘的情况下制作一个模块。

当我尝试使用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中也是一个问题。

我尝试加载的代码只是部分信任。我已经完成了它,并确定它不会导入任何东西或做任何我不喜欢的事情,但我不相信它足以运行它,当我有可以修改的局部变量,并且我不相信自己的代码可以避免我试图导入的代码。

我创建了一个只包含以下内容的空模块:

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模块而言,如果没有写入磁盘,这真的太过分了,但如果你想做到这一点,我相信这是最好的方法。

6 个答案:

答案 0 :(得分:52)

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

答案 1 :(得分:4)

如果模块的代码位于字符串中,您可以放弃StringIO并直接与exec一起使用,如下图所示,文件名为dynmodule.py。 适用于Python 2&amp; 3。

from __future__ import print_function

class _DynamicModule(object):
    def load(self, code):
        execdict = {'__builtins__': None}  # optional, to increase safety
        exec(code, execdict)
        keys = execdict.get(
            '__all__',  # use __all__ attribute if defined
            # else all non-private attributes
            (key for key in execdict if not key.startswith('_')))
        for key in keys:
            setattr(self, key, execdict[key])

# replace this module object in sys.modules with empty _DynamicModule instance
# see Stack Overflow question:
# https://stackoverflow.com/questions/5365562/why-is-the-value-of-name-changing-after-assignment-to-sys-modules-name
import sys as _sys
_ref, _sys.modules[__name__] = _sys.modules[__name__], _DynamicModule()

if __name__ == '__main__':
    import dynmodule  # name of this module
    import textwrap  # for more readable code formatting in sample string

    # string to be loaded can come from anywhere or be generated on-the-fly
    module_code = textwrap.dedent("""\
        foo, bar, baz = 5, 8, 2

        def func():
            return foo*bar + baz

        __all__ = 'foo', 'bar', 'func'  # 'baz' not included
        """)

    dynmodule.load(module_code)  # defines module's contents

    print('dynmodule.foo:', dynmodule.foo)
    try:
        print('dynmodule.baz:', dynmodule.baz)
    except AttributeError:
        print('no dynmodule.baz attribute was defined')
    else:
        print('Error: there should be no dynmodule.baz module attribute')
    print('dynmodule.func() returned:', dynmodule.func())

输出:

dynmodule.foo: 5
no dynmodule.baz attribute was defined
dynmodule.func() returned: 42

'__builtins__'字典中将None条目设置为execdict会阻止代码直接执行任何内置函数,例如__import__ ,因此使运行更安全。您可以通过有选择地向其添加您感觉良好和/或必需的内容来缓解该限制。

还可以添加您希望为代码提供的预定义实用程序和属性,从而为其运行创建自定义执行上下文。这类内容对于实现“插件”非常有用“或其他用户可扩展的架构。

答案 2 :(得分:3)

您可以简单地创建一个Module对象并将其填充到sys.modules中并将代码放入其中。

类似的东西:

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

答案 3 :(得分:1)

从python 3.4开始不推荐使用

imp.new_module

但是使用short solution来自schlenk的types.ModuleType仍在python 3.7中工作

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())来   填写规范中所有丢失的信息

但无论是长版还是短版,都只能设置:__builtins__, __doc__, __loader__, __name__, __package__, __spec__

import sys, importlib

my_name = 'my_module'
my_spec = importlib.util.spec_from_loader(my_name, loader=None)

my_module = importlib.util.module_from_spec(my_spec)

my_code = '''
def f():
    print('f says hello')
'''
exec(my_code, my_module.__dict__)
sys.modules['my_module'] = my_module

my_module.f()

答案 4 :(得分:0)

documentation for imp.load_source说(我的重点):

  

file参数是源文件,从头开始打开以作为文本阅读。 它当前必须是真实的文件对象,而不是用户定义的模拟文件的类。

...所以你可能会对这种方法不满意,我很害怕。

在这种情况下,或许eval对你来说已经足够了吗?

这听起来似乎是一个相当令人惊讶的要求 - 如果你在问题中添加更多关于你真正试图解决的问题,它可能会有所帮助。

答案 5 :(得分:0)

您可以使用execeval以字符串形式执行python代码。请参阅hereherehere