我正在尝试使用py4j打开一个网关,我可以使用该网关将对象从java传递到python。当我尝试使用py4j函数launch_gateway打开网关时,它似乎无法正确连接到我的Java类。但是,当我在命令行中启动我的java类,然后使用JavaGateway在python中连接到它时,一切都会按预期进行。我希望能够使用内置方法,因为我确定我没有考虑py4j设计中已经考虑过的事情,但是我不确定自己做错了什么。

假设我想创建一个通往类sandbox.demo.solver.UtilityReporterEntryPoint.class的网关。在命令行中,我可以通过执行以下操作来做到这一点:

java -cp /Users/grr/anaconda/share/py4j/py4j0.10.4.jar: sandbox.demo.solver.UtilityReporterEntryPoint py4j.GatewayServer


这将按预期启动,连接到网关后,我可以在python中使用类中的方法。到目前为止,一切都很好。

我对py4j文档的理解会让我相信我应该执行以下操作以在python中启动网关:

port = launch_gateway(classpath='sandbox.demo.solver.UtilityReporterEntryPoint')
params = GatewayParameters(port=port)
gateway= JavaGateway(gateway_parameters=params)


执行这三行代码时没有错误,但是当我尝试使用gateway.entry_point.someMethod()访问我的java类方法时,它失败并显示以下错误:


  Py4JError:调用t.getReport时发生错误。跟踪:
  py4j.Py4JException:此网关不存在目标对象ID:t
      在py4j.Gateway.invoke(Gateway.java:277)
      在py4j.commands.AbstractCommand.invokeMethod(AbstractCommand.java:132)
      在py4j.commands.CallCommand.execute(CallCommand.java:79)
      在py4j.GatewayConnection.run(GatewayConnection.java:214)
      在java.lang.Thread.run(Thread.java:745)


显然,在launch_gateway中未正确调用某些内容,或者我向其提供了错误的信息。

launch_gateway的py4j源代码中,您可以看到给定您提供的输入以及由该函数构造的输入,构造了一个命令,最终该命令被subprocess.Popen调用。因此,给定输入上方传递给launch_gateway的输入,传递给Popen的命令将是:

command = ['java', '-classpath', '/Users/grr/anaconda/share/py4j/py4j0.10.4.jar:sandbox.demo.solver.UtilityReporterEntryPoint', 'py4j.GatewayServer', '0']


将此命令传递给Popen将按预期返回侦听端口。但是,连接到此侦听端口仍然不允许访问我的类方法。

最后,将命令作为单个字符串传递给Popen而没有最后一个参数('0'),将正确启动网关,该网关将再次按预期运行。看完py4j.GatewayServer.class的Java源代码后,这没有任何意义,因为main方法似乎表明如果参数的长度为0,则该类应以状态1退出。

在这一点上,我有点茫然。我可以尝试一种可行的解决方案,但是正如我所说,我敢肯定,它会忽略网关行为的重要方面,而且我不喜欢hacky解决方案。我很想在此标签中标记@Barthelemy,但希望他能读到。在此先感谢您的帮助。

编辑

目前,我已经可以通过以下步骤解决此问题。


将包括所有外部依赖项的整个项目打包到单个jar文件magABM-all.jar中,并且将“ Main-Class”设置为UtilityReporterEntryPoint
包括与if...else中的--die-on-exit存在完全相同的GatewayServer.java
使用subprocess.Popen调用命令来运行项目jar。


UtilityReporterEntryPoint.java

public static void main(String[] args) throws IOException {
  GatewayServer server = new GatewayServer(new UtilityReporterEntryPoint());
  System.out.println("Gateway Server Started");
  server.start();
  if (args[0].equals("--die-on-exit")) {
    try {
        BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in, Charset.forName("UTF-8")));
        stdin.readLine();
        System.exit(0);
    } catch (java.io.IOException e) {
        System.exit(1);
    }
  }
}


app.py

def setup_gateway()
    """Launch a py4j gateway using UtilityReporterEntryPoint."""
    process = subprocess.Popen('java -jar magABM-all.jar --die-on-exit', shell=True)
    time.sleep(0.5)
    gateway = JavaGateway()
    return gateway


这样,如果需要,我仍然可以使用gateway.shutdown,并且如果启动py4j网关的python进程死亡或关闭,则网关将关闭。

N.B我绝不认为这是最终的解决方案,因为py4j是由更聪明的人编写的,具有明确的目标,并且我确信有办法在py4j的范围内管理这一确切的工作流程。这只是权宜之计。

最佳答案

有几个问题:


launch_gateway中的classpath参数应该是目录或jar文件,而不是类名。例如,如果要包括其他Java库,则可以将它们添加到classpath参数中。
调用gateway.entry_point.someMethod()时收到的错误表示您没有入口点。当您调用launch_gateway时,JVM将通过GatewayServer.main启动,它将启动没有入口点GatewayServer server = new GatewayServer(null, port)的GatewayServer。当前无法使用launch_gateway并指定入口点。
当您使用java -cp /Users/grr/anaconda/share/py4j/py4j0.10.4.jar: sandbox.demo.solver.UtilityReporterEntryPoint py4j.GatewayServer启动JVM时,我相信JVM使用UtilityReporterEntryPoint作为主要类。尽管您没有提供代码,但我假设该类具有main方法,并且它将使用UtilityReporterEntryPoint实例作为入口点启动GatewayServer。请注意,冒号和类名之间有一个空格,因此UtilityReporterEntryPoint被视为主要类,而不是类路径的一部分。

10-07 18:52