如何从内存加载编译的Python模块?
我需要从一个压缩文件(这个文件是用py2exe压缩生成的)里读取所有的模块(这些模块是预编译好的),然后把它们全部加载到内存中。我知道可以直接从压缩文件里加载,但我想从内存中加载。有没有什么好主意?(我在Windows上使用的是Python 2.5.2)谢谢,Steve
2 个回答
编译后的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。
这要看你手里的“模块(预编译)”具体是什么。假设它就是一个 .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的 import
通常使用的顺序,所以最好模仿这个(这样做没有特定的坏处)。
你可以通过多种方式“创建一个新的模块对象”(例如,使用标准库模块中的 new
和 imp
函数),但“调用类型以获取实例”是现在的标准Python做法,通常从标准库模块 types
中获取类型(除非它有内置名称或者你已经有了),所以我推荐这样做。
最后:
>>> import ciao
>>> ciao.ciao()
'Ciao!'
>>>
...你可以导入这个模块并使用它的函数、类等等。其他的 import
(和 from
)语句会找到这个模块,作为 sys.modules['ciao']
,所以你不需要重复这个操作序列(实际上,如果你只是想确保模块可以从其他地方导入,这里最后的 import
语句是可有可无的——我加上它只是为了展示它能工作;-)。
补充说明:如果你必须以这种方式导入包和模块,而不是像我刚才展示的“普通模块”,也是可以的,但会复杂一些。因为这个回答已经相当长了,我希望你能通过坚持使用普通模块来简化你的生活,所以我就不详细讲这一部分了;-)。
另外请注意,在“从内存多次加载同一个模块”的情况下,这可能会或可能不会达到你想要的效果(每次都会重建模块;你可能想检查一下 sys.modules
,如果模块已经存在就跳过所有操作),特别是在多个线程进行这种重复的“从内存加载”时(需要锁——但更好的架构是有一个专门的线程来执行这个任务,其他模块通过队列与它通信)。
最后,没有讨论如何将这个功能安装为透明的“导入钩子”,它会自动参与 import
语句内部机制——这也是可行的,但并不是你所问的内容,所以在这里,我希望你能通过简单的方式来简化你的生活,就像这个回答所概述的那样。