Загрузить модуль из строки в 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.
Это действительно слишком большая работа для импорта модуля без записи на диск, но если вы когда-нибудь захотите это сделать, я считаю, что это лучший способ.
Ответы
Ответ 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__)