Как загрузить скомпилированные модули Python из памяти? - PullRequest
6 голосов
/ 02 декабря 2009

Мне нужно прочитать все модули (предварительно скомпилированные) из zip-файла (созданного сжатым py2exe) в память и затем загрузить их все. Я знаю, что это может быть сделано путем загрузки непосредственно из zipfile, но мне нужно загрузить их из памяти. Есть идеи? (Я использую Python 2.5.2 на Windows) ТИА Стив

Ответы [ 2 ]

31 голосов
/ 02 декабря 2009

Это зависит от того, что именно у вас есть как «модуль (предварительно скомпилированный)». Давайте предположим, что это именно содержимое файла .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!'
>>> 

... вы можете импортировать модуль и использовать его функции, классы и так далее. Другие операторы importfrom) затем найдут модуль как sys.modules['ciao'], поэтому вам не нужно будет повторять эту последовательность операций (на самом деле вам не нужно это последнее * 1046) * здесь заявление, если все, что вам нужно, это убедиться, что модуль доступен для импорта из других мест - я добавляю его только для того, чтобы показать, что он работает; -).

Редактировать : Если вам абсолютно необходимо импортировать таким образом пакеты и модули из них, а не "простые модули", как я только что показал, это тоже выполнимо, но немного сложнее. Поскольку этот ответ уже довольно длинный, и я надеюсь, что вы можете упростить свою жизнь, придерживаясь простых модулей для этой цели, я собираюсь уклониться от этой части ответа; -).

Также обратите внимание, что это может или не может делать то, что вы хотите в случае «загрузки одного и того же модуля из памяти несколько раз» (это перестраивает модуль каждый раз; вы можете захотеть проверить sys.modules и просто пропустить все, если модуль уже существует) и, в частности, когда такая повторяющаяся «загрузка из памяти» происходит из нескольких потоков (требующих блокировок - но, лучшая архитектура состоит в том, чтобы иметь один выделенный поток, предназначенный для выполнения задачи, с другими модулями, связывающимися с ним через Очередь).

Наконец, не обсуждается вопрос о том, как установить эту функциональность в качестве прозрачной «ловушки импорта», которая автоматически включается в механизмы самих внутренних элементов оператора import - это тоже возможно, но не совсем то, что вы Я спрашиваю о том, что и здесь, я надеюсь, вы также можете упростить свою жизнь, выполняя простые вещи, как показано в этом ответе.

9 голосов
/ 02 декабря 2009

Скомпилированный файл Python состоит из

  1. магическое число (4 байта) для определения типа и версии Python,
  2. отметка времени (4 байта), чтобы проверить, есть ли у нас более новый источник,
  3. маршалированный код объекта.

Чтобы загрузить модуль, вы должны создать объект модуля с помощью 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 для получения дополнительной информации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...