【问题标题】:Packing Python files into a single .py script将 Python 文件打包到单个 .py 脚本中
【发布时间】:2011-05-21 01:30:12
【问题描述】:

有人知道是否有任何工具可以将使用多个文件和模块的 Python 项目打包到一个脚本中吗?

【问题讨论】:

  • 如标题所述,打包成单个 .py 脚本的目的是什么?如果您需要组合(python 本身 + 第三方模块 + 代码)用于安装目的或其他:查看pyinstaller...
  • 我只需要复制一个文件,然后导入或执行。有时复制单个文件比复制整个项目树要方便得多。
  • 也许有可能(例如,如果您将类滥用为命名空间,并且您的代码在作用域/命名空间/导入内部方面不太聪明/hacky),但肯定对开发有害且不需要部署.所以我希望没有人费心制作这样的工具。
  • 如果你只需要一个文件,你可以看看 zipimport (docs.python.org/library/zipimport.html) 模块。使用它你基本上可以做到export PYTHONPATH=mymodules.zip; python -m startmodule 这可能是一个比将所有内容集中在一个大文件中更漂亮的解决方案。
  • 复制或同步整个目录树有什么问题?如果您rsync 它,您将获得仅需要复制新(er)文件的额外好处。这肯定可以很容易地自动化......

标签: python scripting


【解决方案1】:

另存为python_header.py:

#!/bin/env/python
# -*- coding: ascii -*-
import os
import sys
import imp
import tarfile
import tempfile


RUN_MODULE = "__run__"
SENTINEL = 'RzlBTXhya3ljIzl6PFFkQiRKLntEdHF+c2hvWid0IX5NVlxWd' \
           'FxcJ0NWQ2xKVUI0TVEuNl0rWUtnKiRr'.decode('base64')


class FileOffset(object):
    def __init__(self, fileobj, offset=0):
        self._fileobj = fileobj
        self._offset = offset
        self._fileobj.seek(offset)

    def tell(self):
        return self._fileobj.tell() - self._offset

    def seek(self, position, whence=os.SEEK_SET):
        if whence == os.SEEK_SET:
            if position < 0: raise IOErrror("Negative seek")
            self._fileobj.seek(position + self._offset)
        else:
            oldposition = self._fileobj.tell()
            self._fileobj.seek(position, whence)
            if self._fileobj.tell() < self._offset:
                self._fileobj.seek(oldposition, os.SEEK_SET)
                raise IOError("Negative seek")

    def __getattr__(self, attrname):
        return getattr(self._fileobj, attrname)

    def __enter__(self, *args):
        return self._fileobj.__enter__(*args)

    def __exit__(self, *args):
        return self._fileobj.__exit__(*args)


class TarImport(object):
    def __init__(self, tarobj, tarname=None):
        if tarname is None:
            tarname = '<tarfile>'
        self._tarname = tarname
        self._tarobj = tarobj

    def find_module(self, name, path=None):
        module_path = os.path.join(*name.split('.'))
        package_path = os.path.join(module_path, '__init__')

        for path in [module_path, package_path]:
            for suffix, mode, module_type in imp.get_suffixes():
                if module_type != imp.PY_SOURCE:
                    continue
                member = os.path.join(path) + suffix
                try:
                    modulefileobj = self._tarobj.extractfile(member)
                except KeyError:
                    pass
                else:
                    return Loader(name, modulefileobj,
                                  "%s/%s" % (self._tarname, member),
                                  (suffix, mode, module_type))


class Loader(object):
    def __init__(self, name, fileobj, filename, description):
        self._name = name
        self._fileobj = fileobj
        self._filename = filename
        self._description = description

    def load_module(self, name):
        imp.acquire_lock()
        try:
            module = sys.modules.get(name)
            if module is None:
                module = imp.new_module(name)

            module_script = self._fileobj.read()
            module.__file__ = self._filename
            module.__path__ = []
            sys.modules[name] = module
            exec(module_script, module.__dict__, module.__dict__)
        finally:
            imp.release_lock()

        return module


def find_offset(fileobj, sentinel):
    read_bytes = 0
    for line in fileobj:
        try:
            offset = line.index(sentinel)
        except ValueError:
            read_bytes += len(line)
        else:
            return read_bytes + offset + len(sentinel)
    raise ValueError("sentinel not found in %r" % (fileobj, ))


if __name__ == "__main__":
    sys.argv[:] = sys.argv[1:]
    archive_path = os.path.abspath(sys.argv[0])
    archive_offset = find_offset(open(archive_path), SENTINEL)

    archive = FileOffset(open(archive_path), archive_offset)

    tarobj = tarfile.TarFile(fileobj=archive)
    importer = TarImport(tarobj, archive_path)

    sys.meta_path.insert(0, importer)

    importer.find_module(RUN_MODULE).load_module(RUN_MODULE)

另存为sh_header.sh

#!/bin/sh

head -n @@TO@@ "$0" | tail -n +@@FROM@@ | python - "$0"

exit $?

另存为create_tarred_program.py:

#!/usr/bin/env python
# -*- coding: latin-1 -*-

import sys
import imp
import shutil

sh_filename, runner_filename, tar_archive, dst_filename = sys.argv[1:]

runner = imp.load_module("tarfile_runner",
                        open(runner_filename, 'U'),
                        runner_filename,
                        ('.py', 'U', imp.PY_SOURCE))



sh_lines = open(sh_filename, 'r').readlines()
runner_lines = open(runner_filename, 'r').readlines()

sh_block = ''.join(sh_lines)
runner_block = ''.join(runner_lines)

if runner.SENTINEL in runner_block or runner.SENTINEL in sh_block:
    raise ValueError("Can't have the sentinel inside the runner module")
if not runner_block.endswith('\n') or not sh_block.endswith('\n'):
    raise ValueError("Trailing newline required in both headers")

to_pos = len(sh_lines) + len(runner_lines)
from_pos = len(sh_lines) + 1

sh_block = sh_block.replace("@@TO@@", str(to_pos))
sh_block = sh_block.replace("@@FROM@@", str(from_pos))


dst = open(dst_filename, 'wb')

dst.write(sh_block)
dst.write(runner_block)
dst.write(runner.SENTINEL)

shutil.copyfileobj(open(tar_archive, 'rb'), dst)

dst.flush()
dst.close()    

使用名为 packages.tar 的包创建一个 tar 存档。主模块应该叫做__run__.py,你不应该导入__main__。运行:

create_tarred_program.py sh_header.sh python_header.py packages.tar program.sh

分发program.sh

可以通过扩展的第一行来避免对/bin/sh 的依赖,但它仍然不能在除*nix 之外的任何东西上工作,所以没有任何意义。

【讨论】:

  • 您在这里似乎做了很多出色的工作。这段代码在 PyPI 等其他地方是否可用?
【解决方案2】:

制作 .egg 文件并安装或放在 pythonpath 上可能会解决您的问题。
similar fellow

【讨论】:

    【解决方案3】:

    【讨论】:

    • 链接已损坏。
    猜你喜欢
    • 2020-10-05
    • 1970-01-01
    • 2021-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多