如何从内存加载已编译的python模块?

时间:2009-12-02 04:53:21

标签: python module

我需要从zipfile(由py2exe压缩构建)读取所有模块(预编译)到内​​存中然后加载它们。 我知道这可以通过从zipfile直接加载来完成,但我需要从内存中加载它们。 有任何想法吗? (我在Windows上使用python 2.5.2) TIA Steve

2 个答案:

答案 0 :(得分:31)

这取决于你所拥有的“模块(预编译)”。我们假设它正是.pyc文件的内容,例如,ciao.pyc由以下内容构建:

$ cat>'ciao.py'
def ciao(): return 'Ciao!' 
$ python -c'import ciao; print ciao.ciao()'
Ciao!

我已经建立了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自己的{{}} {{ 1}}通常使用,因此最好模仿它(没有特定的缺点)。

您可以通过多种方式“制作新的模块对象”(例如,来自importnew等标准库模块中的函数,但是“调用类型来获取实例”是这些天正常的Python方式,以及从中获取类型的正常位置(除非它有内置名称,否则你已经很方便)来自标准库模块imp,这就是我推荐的

现在,最后:

types

...您可以导入模块并使用其功能,类等。然后,其他>>> import ciao >>> ciao.ciao() 'Ciao!' >>> (和import)语句将找到模块from,因此您无需重复此操作序列(实际上您不需要 / em>这里的最后一个sys.modules['ciao']语句如果你想要的是确保模块可以从其他地方导入 - 我只是为了表明它有用而添加它; - )。

编辑:如果你绝对必须以这种方式导入包和模块,而不是像我刚刚展示的“普通模块”那样,那也是可行的,但有点复杂。由于这个答案已经很长了,我希望你可以通过坚持使用普通模块来简化你的生活,我会推卸答案的那一部分; - )。

另请注意,在“多次从内存加载相同模块”的情况下,这可能会或可能不会执行您想要的操作(每次重建模块;您可能需要检查sys.modules并且只是跳过所有内容,如果模块已经存在),特别是当从多个线程(需要锁定)发生这样的重复“从内存加载”时 - 但是,更好的架构是有一个专用线程专门用于执行任务,其他模块通过队列)。

最后,没有讨论如何将此功能安装为透明的“导入钩子”,它自动参与import语句内部机制本身 - 这也是可行的,但不完全是你的我在这里问,所以在这里,我希望你可以通过简单的方式来简化你的生活,正如这个答案所概述的那样。

答案 1 :(得分:9)

编译后的Python文件由

组成
  1. 幻数(4个字节),用于确定Python的类型和版本,
  2. 时间戳(4个字节)来检查我们是否有更新的源,
  3. 封送代码对象。
  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