2010-12-06 9 views
5

¿Alguien sabe si hay alguna herramienta para empaquetar un proyecto de Python que use varios archivos y módulos en un solo script?Embalaje de archivos de Python en un solo script .py

+1

¿Cuál es el propósito de embalaje en un único script .py como se indica en el título? Si necesita combinar (python en sí mismo + módulos de terceros + código) para fines de instalación u otro: consulte 'pyinstaller' ... – ChristopheD

+0

Necesito poder copiar solo un archivo y luego importarlo o ejecutarlo. A veces es mucho más conveniente copiar un único archivo en lugar de copiar todo el árbol del proyecto. –

+0

Posiblemente sea posible (si, por ejemplo, abusó de las clases como espacios de nombres y si su código no es demasiado ingenioso/hacky con respecto al alcance/espacios de nombres/importación interna), pero ciertamente nocivo para el desarrollo y no necesario para la implementación. Así que espero que nadie se moleste en hacer tal herramienta. – delnan

Respuesta

3

Guardar esto como 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) 

Guardar esto como sh_header.sh:

#!/bin/sh 

head -n @@[email protected]@ "$0" | tail -n [email protected]@[email protected]@ | python - "$0" 

exit $? 

Guardar esto como 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("@@[email protected]@", str(to_pos)) 
sh_block = sh_block.replace("@@[email protected]@", 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()  

Crear un tar una rchive con sus paquetes llamados packages.tar. El módulo principal se debe llamar __run__.py, nunca debe importar __main__. Ejecutar:

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

distrubute program.sh.

Es posible evitar la dependencia de /bin/sh por una primera línea extendida, pero aún así no funcionará en nada excepto * nix, por lo que no tiene sentido.

+0

Parece que ha hecho un gran trabajo aquí. ¿Este código está disponible en otro lugar, como PyPI? –

Cuestiones relacionadas