问题

当使用 PEX (https://github.com/pantsbuild/pex) 来打包一个 Python 项目时,会发现有些动态的源码访问方式无法使用。例如下面这段代码,通过动态遍历 Python 源文件来载入一个 package 下的所有模块:

# -*- coding: utf-8 -*-

import glob
from os.path import dirname, basename, join, isfile

modules = glob.glob(join(dirname(__file__), "*.py"))
__all__ = [
    basename(f)[:-3] for f in modules if isfile(f) and f != "__init__.py"
]

from . import *  # noqa

当使用 PEX 进行打包的时候,程序默认会使用 --zip-safe 参数,即在 PEX 程序执行的过程中,源码不会存在到磁盘上,因此 glob.glob() 会返回一个空列表,所以就无法载入任何模块。如果遇到这个情况,当你需要访问某些子模块时,就会触发如下的错误:

AttributeError: 'module' object has no attribute 'submodule_name'

解决方案

如果要支持这样的代码逻辑,需要使用 PEX 的 --not-zip-safe 模式。当启用该模式时,PEX 在运行程序前,会先把程序的代码解压到 ${PEX_ROOT}/code 目录下 ( PEX_ROOT 可以通过 --pex-root 参数指定,默认值是 ~/.pex/),然后再运行程序。这样代码中就可以使用上面这样的逻辑来动态的访问源码文件了。

但是,使用这个方法有个问题,就是每运行一次,代码就会被保留一份到 ${PEX_ROOT}/code 目录下一次,不仅会占用空间,而且会让别人可以方便的访问到源代码(虽然,直接解压 PEX 文件也能获得源代码)。所以,可以使用类似如下的代码,在程序退出前删除 PEX 解压出来的源代码:

def clean_pex_code_dir(f):
    # We use default PEX_ROOT setting.
    pex_root = os.path.expanduser("~/.pex")
    code_path = os.path.join(pex_root, "code")

    def deco(*args, **kwargs):
        try:
            return f(*args, **kwargs)
        except Exception as e:
            # It's necessary to raise catched exception for dumping traceback.
            raise e
        finally:
            if os.path.exists(code_path):
                shutil.rmtree(code_path)

    return deco


@clean_pex_code_dir
def main():
    pass


if __name__ == "__main__":
    main()

知识共享许可协议本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。