Подтвердить что ты не робот

Загрузить модуль из строки в 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 в файл без записи на диск?

UPDATE:

ПРИМЕЧАНИЕ. Эта проблема также является проблемой в 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.

Это действительно слишком большая работа для импорта модуля без записи на диск, но если вы когда-нибудь захотите это сделать, я считаю, что это лучший способ.

4b9b3361

Ответ 1

Вот как импортировать строку в качестве модуля (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

Ответ 2

Если код модуля находится в строке, вы можете отказаться от использования StringIO и использовать его непосредственно с exec, как показано ниже, с файлом с именем dynmodule.py.
Работает на Python 2 и 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 Qaru question: http://bit.ly/ff94g6)
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 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__, и поэтому делает его более безопасным. Вы можете облегчить это ограничение, выборочно добавляя к нему вещи, которые вы чувствуете в порядке и/или требуются.

Также можно добавить свои собственные предопределенные утилиты и атрибуты, которые вы хотели бы сделать доступными для кода, тем самым создав пользовательский контекст выполнения для его запуска. Подобная вещь может быть полезна для реализации "плагина" "или другой расширяемой пользователем архитектуры.

Ответ 3

Документация для imp.load_source говорит (мой акцент):

Аргумент файла - это исходный файл, открытый для чтения как текст, с самого начала. В настоящий момент он должен быть реальным файловым объектом, а не определяемым пользователем классом, эмулирующим файл.

... так что вам может быть не повезло с этим методом, я боюсь.

Возможно, eval будет достаточно для вас в этом случае?

Это звучит как довольно удивительное требование, хотя это может помочь, если вы добавите еще немного своего вопроса о проблеме, которую вы действительно пытаетесь решить.

Ответ 4

вы можете использовать exec или eval для выполнения кода python в виде строки. см. здесь, здесь и здесь

Ответ 5

Вы можете просто создать объект модуля и вставить его в sys.modules и поместить свой код внутрь.

Что-то вроде:

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