将一组python文件分发为单个可执行文件

时间:2015-09-21 12:57:49

标签: python

我有一个python脚本,分布在几个文件中,都在一个目录中。我想将它作为单个可执行文件(主要用于Linux系统)分发,以便可以移动文件并轻松复制。

我已将主文件重命名为__main__.py,并将所有内容压缩为myscript.zip,因此现在我可以运行python myscript.zip。但这一步太短了。我想以./myscript运行它,而不创建别名或包装器脚本。

这有可能吗?最好的方法是什么?我认为也许zip文件可以嵌入到(ba)sh脚本中,将其传递给python(但如果可能的话,不要创建临时文件)。

编辑: 再过一次setuptools(我之前没有设法让它工作),我可以创建一种自包含的脚本,一个" eggsecutable script"。诀窍在于,在这种情况下,您不能将主模块命名为__main__.py。然后仍然存在一些问题:生成的脚本无法重命名,并且在运行时仍会创建__pycache__目录。我通过在文件开头修改shell脚本代码并在那里将py -B标志添加到python命令来解决这些问题。

编辑(2):也不是那么容易,因为我仍然在" eggsecutable"旁边有我的源.py文件,移动一些东西然后停止工作。

4 个答案:

答案 0 :(得分:1)

您可以将原始zip文件编辑为二进制文件,然后将shebang插入第一行。

#!/usr/bin/env python
PK...rest of the zip

当然,您需要一个适当的编辑器来处理二进制文件(例如:vim -b),或者您可以使用一个小的bash脚本来创建它。

{ echo '#!/usr/bin/env python'; cat myscript.zip; } > myscript
chmod +x myscript
./myscript

答案 1 :(得分:0)

首先,有必要的"因此不是通常的事情,你确定你想这样做吗?"警告。那就是说,回答你的问题,而不是试图用别人认为你应该做的事来代替......

您可以编写脚本,并将其添加到python egg中。该脚本将从自身中提取蛋,显然在遇到egg文件数据之前调用exit。 Egg文件是可导入的但不可执行,因此脚本必须

  1. 将鸡蛋自身提取为当前目录中已知名称的鸡蛋文件
  2. 运行python -m egg
  3. 删除文件
  4. 退出
  5. 抱歉,我现在正打电话,所以我稍后会用实际代码更新

答案 2 :(得分:0)

继续我自己的尝试,我编造了一些对我有用的东西:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import glob, os.path

from setuptools import setup

files = [os.path.splitext(x)[0] for x in glob.glob('*.py')]
thisfile = os.path.splitext(os.path.basename(__file__))[0]
files.remove(thisfile)

setup(
  script_args=['bdist_wheel', '-d', '.'],
  py_modules=files,
)

# Now try to make the package runnable:
# Add the self-running trick code to the top of the file,
# rename it and make it executable

import sys, os, stat

exe_name = 'my_module'
magic_code = '''#!/bin/sh
name=`readlink -f "$0"`
exec {0} -c "import sys, os; sys.path.insert(0, '$name'); from my_module import main; sys.exit(main(my_name='$name'))" "$@"
'''.format(sys.executable)

wheel = glob.glob('*.whl')[0]
with open(exe_name, 'wb') as new:
  new.write(bytes(magic_code, 'ascii'))
  with open(wheel, 'rb') as original:
    data = True
    while (data):
      data = original.read(4096)
      new.write(data)
os.remove(wheel)
st = os.stat(exe_name)
os.chmod(exe_name, st.st_mode | stat.S_IEXEC)

这将创建一个包含当前目录中所有* .py文件的轮子(除了它自己),然后添加代码以使其可执行。 exe_name是文件的最终名称,from my_module import main; sys.exit(main(my_name='$name'))应根据每个脚本进行修改,在我的情况下,我想从main调用my_module.py方法,接受参数my_name(正在运行的实际文件的名称)。

不能保证它会在与创建它的系统不同的系统中运行,但是从源创建一个自包含的文件(例如放在~/bin中)仍然很有用。

答案 3 :(得分:0)

另一个不太讨厌的解决方案(我很抱歉两次回答我自己的问题,但这不符合评论,我认为在单独的方框中更好)。

#!/usr/bin/env python3

modules = [
  [ 'my_aux', '''
def my_aux():
  return 7
'''],
  ['my_func', '''
from my_aux import my_aux
def my_func():
  print("and I'm my_func: {0}".format(my_aux()))
'''],
  ['my_script', '''
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
import sys
def main(my_name):
  import my_aux
  print("Hello, I'm my_script: {0}".format(my_name))
  print(my_aux.my_aux())
  import my_func
  my_func.my_func()
if (__name__ == '__main__'):
  sys.exit(main(__file__))
'''],
]

import sys, types
for m in modules:
  module = types.ModuleType(m[0])
  exec(m[1], module.__dict__)
  sys.modules[m[0]] = module
del modules

from my_script import main

main(__file__)

我认为这更为明确,尽管可能效率较低。所有需要的文件都包含在字符串中(为了提高空间效率,可以先将它们压缩并进行b64编码)。然后将它们作为模块导入,并运行main方法。应该注意以正确的顺序定义模块。