Как загрузить скомпилированные модули Python из памяти?
Мне нужно прочитать все модули (предварительно скомпилированные) из zipfile (созданный сжатым py2exe) в память, а затем загрузить их все. Я знаю, что это может быть сделано путем загрузки непосредственно из zipfile, но мне нужно загрузить их из памяти. Есть идеи? (Я использую Python 2.5.2 на Windows) TIA Steve
2 ответа
Это зависит от того, что именно у вас есть как "модуль (предварительно скомпилированный)". Давайте предположим, что это именно содержимое .pyc
файл, например, ciao.pyc
как построено:
$ cat>'ciao.py'
def ciao(): return 'Ciao!'
$ python -c'import ciao; print ciao.ciao()'
Ciao!
IOW, построив таким образом ciao.pyc
скажи, что ты сейчас делаешь:
$ python
Python 2.5.1 (r251:54863, Feb 6 2009, 19:02:12)
[GCC 4.0.1 (Apple Inc. build 5465)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> b = open('ciao.pyc', 'rb').read()
>>> len(b)
200
и ваша цель состоит в том, чтобы перейти от этой строки байтов b
в импортируемый модуль ciao
, Вот как:
>>> import marshal
>>> c = marshal.loads(b[8:])
>>> c
<code object <module> at 0x65188, file "ciao.py", line 1>
Вот как вы получаете объект кода из .pyc
двоичное содержимое Изменить: если вам любопытно, первые 8 байтов - это "магическое число" и временная метка - здесь не требуется (если только вы не хотите проверять их работоспособность и вызывать исключения, если это оправдано, но это выходит за рамки вопроса; marshal.loads
в любом случае повысит, если обнаружит поврежденную строку).
Затем:
>>> import types
>>> m = types.ModuleType('ciao')
>>> import sys
>>> sys.modules['ciao'] = m
>>> exec c in m.__dict__
то есть: сделать новый объект модуля, установить его в sys.modules
, заполнить его, выполнив объект кода в его __dict__
, Изменить: порядок, в котором вы делаете sys.modules
вставка и exec
имеет значение тогда и только тогда, когда у вас может быть циклический импорт - но это порядок самого Python import
обычно использует, так что лучше подражать (что не имеет конкретных недостатков).
Вы можете "создать новый объект модуля" несколькими способами (например, из функций в стандартных модулях библиотеки, таких как new
а также imp
), но "вызов типа для получения экземпляра" в наши дни является обычным способом Python, и обычное место, где можно получить тип (если у него нет встроенного имени или у вас его нет под рукой), взято из стандартного библиотечный модуль types
вот что я рекомендую.
Теперь, наконец:
>>> import ciao
>>> ciao.ciao()
'Ciao!'
>>>
... вы можете импортировать модуль и использовать его функции, классы и так далее. Другой import
(а также from
) операторы найдут модуль как sys.modules['ciao']
, поэтому вам не нужно будет повторять эту последовательность операций (на самом деле вам не нужно это последнее import
здесь, если все, что вам нужно, это убедиться, что модуль доступен для импорта из других мест - я добавляю его только для того, чтобы показать, что он работает;-).
Изменить: Если вам абсолютно необходимо импортировать таким образом пакеты и модули из них, а не "простые модули", как я только что показал, это тоже выполнимо, но немного сложнее. Поскольку этот ответ уже довольно длинный, и я надеюсь, что вы можете упростить свою жизнь, придерживаясь простых модулей для этой цели, я собираюсь уклониться от этой части ответа;-).
Также обратите внимание, что это может или не может делать то, что вы хотите в случае "загрузки одного и того же модуля из памяти несколько раз" (это перестраивает модуль каждый раз; вы можете захотеть проверить sys.modules и просто пропустить все, если модуль уже существует) и, в частности, когда такая повторяющаяся "загрузка из памяти" происходит из нескольких потоков (требующих блокировок, но лучшей архитектурой является наличие одного выделенного потока, предназначенного для выполнения задачи, с другими модулями, связывающимися с ней через очередь).
Наконец, не обсуждается, как установить эту функциональность в качестве прозрачного "крючка импорта", который автоматически включается в механизмы import
Сами внутренние утверждения - это тоже выполнимо, но не совсем то, о чем вы спрашиваете, так что и здесь, я надеюсь, вы также можете упростить свою жизнь, выполняя простые действия, как показано в этом ответе.
Скомпилированный файл Python состоит из
- магическое число (4 байта) для определения типа и версии Python,
- отметка времени (4 байта), чтобы проверить, есть ли у нас более новый источник,
- маршаледовый код объекта.
Для загрузки модуля необходимо создать объект модуля с imp.new_module()
выполнить немаслаженный код в пространстве имен нового модуля и поместить его в sys.modules
, Ниже в примере реализации:
import sys, imp, marshal
def load_compiled_from_memory(name, filename, data, ispackage=False):
if data[:4]!=imp.get_magic():
raise ImportError('Bad magic number in %s' % filename)
# Ignore timestamp in data[4:8]
code = marshal.loads(data[8:])
imp.acquire_lock() # Required in threaded applications
try:
mod = imp.new_module(name)
sys.modules[name] = mod # To handle circular and submodule imports
# it should come before exec.
try:
mod.__file__ = filename # Is not so important.
# For package you have to set mod.__path__ here.
# Here I handle simple cases only.
if ispackage:
mod.__path__ = [name.replace('.', '/')]
exec code in mod.__dict__
except:
del sys.modules[name]
raise
finally:
imp.release_lock()
return mod
Обновление: код обновляется для правильной обработки пакетов.
Обратите внимание, что вам нужно установить обработчик импорта для обработки импорта внутри загруженных модулей. Один из способов сделать это - добавить ваш искатель в sys.meta_path
, См. PEP302 для получения дополнительной информации.