使用Jython将我的Python脚本打包为JAR文件进行分发?

59

我已经成为一名Python程序员将近两年了,习惯使用小脚本来自动化我在办公室必须完成的一些重复任务。现在,显然我的同事们也注意到了这一点,并希望得到这些脚本。

他们中有些人使用Mac电脑,有些使用Windows电脑;而我是在Windows上编写的这些脚本。我调查了使用py2exe甚至py2app制作我的脚本的可执行文件的可能性,但它们从未令我满意...

我了解到他们所有人的系统上都有JVM,那么我能否使用Jython之类的工具将我的脚本打包成一个单独的可执行JAR文件呢?

这种做法可行吗...我的意思是,我不知道如何为Jython编写脚本,当我编写这些脚本时也不关心它...这会带来什么问题?

4个回答

66
目前在jar中分发Python文件的最佳技术方法已在Jython的维基上详细介绍:http://wiki.python.org/jython/JythonFaq/DistributingJythonScripts 对于您的情况,我认为您需要获取安装Jython时获得的jython.jar文件,并将Jython Lib目录压缩到其中,然后压缩您的 .py 文件,并添加一个带有启动逻辑的__run__.py文件(此文件由Jython特别处理,并将在使用“java -jar”调用jar时执行)。
这个过程肯定比它应该的要复杂,因此我们(Jython开发人员)需要想出一个很好的工具来自动化这些任务,但就目前而言,这些是最好的方法。下面我将复制上述文章底部的配方(略作修改以适应您的问题描述),以让您了解解决方案。
创建基本的jar:
$ cd $JYTHON_HOME
$ cp jython.jar jythonlib.jar
$ zip -r jythonlib.jar Lib

将其他模块添加到JAR包中:

$ cd $MY_APP_DIRECTORY
$ cp $JYTHON_HOME/jythonlib.jar myapp.jar
$ zip myapp.jar Lib/showobjs.py
# Add path to additional jar file.
$ jar ufm myapp.jar othermanifest.mf

添加 __run__.py 模块:

# Copy or rename your start-up script, removing the "__name__  == '__main__'" check.
$ cp mymainscript.py __run__.py
# Add your start-up script (__run__.py) to the jar.
$ zip myapp.jar __run__.py
# Add path to main jar to the CLASSPATH environment variable.
$ export CLASSPATH=/path/to/my/app/myapp.jar:$CLASSPATH

在MS Windows上,最后一行设置CLASSPATH环境变量的代码应该是这样的:

set CLASSPATH=C:\path\to\my\app\myapp.jar;%CLASSPATH%

或者,在Windows操作系统上,使用控制面板和系统属性来设置CLASSPATH环境变量。

运行应用程序:

$ java -jar myapp.jar mymainscript.py arg1 arg2

或者,如果您已将启动脚本添加到Jar文件中,请使用以下之一:

$ java org.python.util.jython -jar myapp.jar arg1 arg2
$ java -cp myapp.jar org.python.util.jython -jar myapp.jar arg1 arg2
$ java -jar myapp.jar -jar myapp.jar arg1 arg2

使用双-jar有点烦人,如果你想避免那个并获得更令人愉悦的:

$ java -jar myapp.jar arg1

在未来的Jython版本中我们需要再做更多的工作才能实现类似下面这样的操作 [更新:JarRunner已成为Jython 2.5.1的一部分]。以下是一些Java代码,它会自动查找__run__.py文件并运行它。请注意,这是我第一次编写这个类。如果需要改进,请让我知道!

package org.python.util;

import org.python.core.imp;
import org.python.core.PySystemState;

public class JarRunner {

    public static void run(String[] args) {
        final String runner = "__run__";
        String[] argv = new String[args.length + 1];
        argv[0] = runner;
        System.arraycopy(args, 0, argv, 1, args.length);
        PySystemState.initialize(PySystemState.getBaseProperties(), null, argv);
        imp.load(runner);
    }

    public static void main(String[] args) {
        run(args);
    }
}

我将这段代码放入org.python.util包中,因为如果我们决定在未来的Jython中包含它,那么它应该放在那里。要编译它,你需要像下面这样将jython.jar(或myapp.jar)放入类路径中:

$ javac -classpath myapp.jar org/python/util/JarRunner.java

那么您将需要将JarRunner.class添加到您的jar文件中(该类文件需要位于org/python/util/JarRunner.class中),在“org”目录上调用jar将整个路径添加到您的jar文件中。

$ jar uf org
将这段代码添加到一个文件中,用于更新清单文件。一个好的文件名是manifest.txt:
Main-Class: org.python.util.JarRunner

然后更新jar文件的清单(manifest):

$ jar ufm myapp.jar manifest.txt

现在,您应该能够像这样运行您的应用程序:

$ java -jar myapp.jar

嗨,弗兰克,谢谢你的见解,真的。有一件事,我在命令“zip myapp.jar Lib/showobjs.py”处遇到了错误。它应该做什么? - sharat87
好的,我错了 - 你确实需要两次调用分布式jar包,这样你最终会得到:"java -jar myapp.jar -jar myapp.jar arg1"。这非常不令人满意,而且似乎不太难修复。我会把它放在我的待办事项清单上,在2.5.1版本中进行检查。 - Frank Wierzbicki
顺便说一句,我会回来并提供某种解决方案。我认为基于org.python.util.jython的自定义Java类和修改后的清单文件可以解决问题。可能需要一两天时间才能找到时间,但请继续关注 :) - Frank Wierzbicki
它说__run__.py被添加到存档中,但是当你调用它时,你告诉它执行mymainscript.py...这是一个打字错误吗?还是应该像那样?如果是应该这样的话,有人能解释一下为什么吗? - searchengine27
我的当前版本的Jython(2.7)为什么要寻找__main__.py而不是__run__.py - Justin
显示剩余5条评论

2
我遇到了类似的问题,我想为我的Jython应用程序创建简单的命令行调用,而不需要用户经过Jython安装过程,并且能够使Jython脚本在运行时将库依赖项附加到sys.path中,以包括核心Java代码。
# append Java library elements to path
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "lib", "poi-3.8-20120326.jar"))

当在Unix系统上显式在命令行上运行“jython”启动器时,它只是运行一个大的shell脚本来正确地形成一个Java命令行调用。这个jython启动器似乎依赖于回到一个核心安装的jython,并通过某种魔法方式允许在我的.py脚本内运行时添加到sys.path的.jar文件的正确处理。您可以看到调用是什么,并通过以下方式阻止执行:
jython --print run_form.py
java -Xmx512m -Xss1024k -Dfile.encoding=UTF-8 -classpath /Applications/jython2.5.2/jython.jar: -Dpython.home=/Applications/jython2.5.2 -Dpython.executable=/Applications/jython2.5.2/bin/jython org.python.util.jython run_form.py

但这仍然只是启动 JVM 并运行一个类文件。因此,我的目标是能够调用分发的 lib 目录中独立的 jython.jar,以便用户无需执行任何额外的安装步骤即可开始使用我的 .py 脚本实用程序。
java -Xmx512m -Xss1024k -classpath ../../lib/jython.jar org.python.util.jython run_form.py

问题在于行为差异很大,我会得到这样的回应:
  File "run_form.py", line 14, in <module>
    import xls_mgr
  File "/Users/test/Eclipse/workspace/test_code/py/test/xls_mgr.py", line 17, in <module>
    import org.apache.poi.hssf.extractor as xls_extractor
ImportError: No module named apache

现在你可能会说我应该将jar文件添加到-classpath中,事实上我尝试过,但结果仍然相同。
将所有的.class文件捆绑在一个jython.jar中的建议对我来说一点也不吸引人。这样做会很混乱,并且会使Java/Python混合应用程序与jython发行版过于紧密地绑定。因此,这个想法行不通。最后,在大量搜索之后,我发现了jython.org上的bug #1776,这个问题已经被列为关键问题一年半了,但我没有看到最新的jython更新包含了修复。不过,如果你在使用jython时遇到了问题,无法包含你的独立jar文件,你应该阅读这篇文章。

http://bugs.jython.org/issue1776

在那里,你会找到这个临时解决方案。在我的情况下,我将Apache POI jar文件解压到了它自己的独立lib目录中,然后修改了sys.path条目,使其指向该目录而不是jar文件:
sys.path.append('/Users/test/Eclipse/workspace/test_code/lib/poi_lib')

现在,当我通过Java引用本地的jython.jar来运行Jython时,实用程序就可以正常运行了。现在我可以创建简单的脚本或批处理文件,为我的.py实用程序创建无缝的命令行体验,用户可以在没有任何额外安装步骤的情况下运行它们。

这是一篇很好的文章,感谢您分享您的经验和知识。我也一直在使用Jython,并遇到了与您相同的问题。(+1) - Eder

1
为了以一种不需要本地Python安装的方式分发您的Python脚本,您也可以尝试使用Nuitka,它基本上将您的Python代码翻译成C++代码,然后编译为真正的本地二进制文件。

1

3
谢谢提供链接,我之前不知道jythonc,但是我刚刚了解到jythonc已经不再维护了。http://www.jython.org/archive/22/jythonc.html。我找不到任何关于如何使用jython命令来制作jar包的帮助...感激任何关于此的帮助。 - sharat87

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接