Py4j原理及简单使用

来源:互联网 发布:pat端口地址转换 编辑:程序博客网 时间:2024/06/05 02:37

搞了两三个礼拜Jpype,感觉不是很给力,在Django中一直运行不起来,应该是本人技术水平过于低下吧。

今天又见到了貌似可用的Py4j,貌似也可以用于Python调用Java函数,姑且先调研一下。

Py4j sourceforge网址:http://py4j.sourceforge.net/index.html

或者python官网中页面:http://pypi.python.org/pypi/Py4J

邮件列表:https://lists.sourceforge.net/lists/listinfo/py4j-users

官方文档:http://py4j.sourceforge.NET/contents.html

一、        Py4j介绍

Py4j可以使运行于python解释器的python程序动态的访问java虚拟机中的java对象。Java方法可以像java对象就在python解释器里一样被调用,Javacollection也可以通过标准python collection方法调用。Py4j也可以使java程序回调python对象。

下面是一个简单的py4j应用例子。

>>> from py4j.java_gateway import JavaGateway

>>> gateway = JavaGateway()                   # connect to the JVM

>>> random = gateway.jvm.java.util.Random()   # create a java.util.Random instance

>>> number1 = random.nextInt(10)              # call the Random.nextInt method

>>> number2 = random.nextInt(10)

>>> print(number1,number2)

(2, 7)

>>> addition_app = gateway.entry_point        # get the AdditionApplication instance

>>> addition_app.addition(number1,number2)    # call the addition method

9

上面的例子中,python程序从JVM中创建了一个java.util.Random实例,并且调用了一些方法。并且也访问了一个普通的java类。AdditionApplication,用以求产生的两个数字的和。

下面是AdditionApplication代码,注意java程序必须在python代码执行前就已经开始了。也就是说py4j并不会开启JVM。

public class AdditionApplication {
 
  public int addition(int first, int second) {
    return first + second;
  }
 
  public static void main(String[] args) {
    AdditionApplication app = new AdditionApplication();
    // app is now the gateway.entry_point
    GatewayServer server = new GatewayServer(app);
    server.start();
  }
}

 

 

二、        安装Py4j

1、            安装python2.6+或者3.1+。Py4j是一个用python和java写的库,已经在python2.6,2.7,3.1,3.2上测试过了。

2、            安装java 6。当然你也要安装java 6,并且如果你想使用java编译器,你就必须安装一个jdk。如果你在使用其他的编译器,就像eclipse开发环境提供的编译器那种,你只需使用jre就好了。

3、            安装 Py4j

第一种方法是使用easy_install。直接运行easy_install py4j,当然当你的系统是Linux或unix时,并且你又希望py4j是系统范围都能用的,就在前面加上sudo;布置py4j的环境变量,并且必须在PYTHONPATH中必须存在;Py4j的java库位于share/py4j/py4j0.x.jar。这个取决于你的系统。对于linux系统,作为系统级的库,就会在/usr/share/py4j/py4j0.x.jar;而对于windows么就在C:\python27\share\py4j\py4j0.x.jar

第二种方法是使用官方发行版。可以从sourceforge或者python官网上下载源码。如果是linux或unix系统,下载tar.gz文件;如果是windows系统下载zip版本。解压缩并进入文件目录。运行python setup.py install命令(系统级的话加上sudo)。将Py4j环境变量加入PYTHONPATH。Py4j的java库位于py4j-java/py4j0.x.jar中,使用java程序时将其加入你的classpath。

第三种方法么就是用Git或者Apache ant或者Sphinx或者nose

Git:gitclonehttps://github.com/bartdag/py4j.gitpy4j

 

三、        开始使用Py4j

1、            java程序部分

下面的是一个Stack类,提供四种服务:push一个元素到stack的头部;pop出stack顶部的元素;得到包含这个stack的list;push list中的所有元素。

package py4j.examples;
 
import java.util.LinkedList;
import java.util.List;
 
public class Stack {
    private List<String> internalList = new LinkedList<String>();
 
    public void push(String element) {
        internalList.add(0, element);
    }
 
    public String pop() {
        return internalList.remove(0);
    }
 
    public List<String> getInternalList() {
        return internalList;
    }
 
    public void pushAll(List<String> elements) {
        for (String element : elements) {
            this.push(element);
        }
    }
}

要使python能使用这个stack类,必须要:访问正在运行上述程序的JVM;访问JVM中你创建的对象。这两个方面可以由两个对象实现。第一个对象就是GatewayServer实例:它允许python程序通过本地网络socket来与JVM通信。第二个对象就是一个实体指针(entry point),它可以是任意对象(Façade, singleton, list等)。

GatewayServer可以配置网络地址和端口,也可以使用默认设置。

下面是实例指针的例子,允许访问预配置过的stack

package py4j.examples;
 
import py4j.GatewayServer;
 
public class StackEntryPoint {
 
    private Stack stack;
 
    public StackEntryPoint() {
      stack = new Stack();
      stack.push("Initial Item");
    }
 
    public Stack getStack() {
        return stack;
    }
 
    public static void main(String[] args) {
        GatewayServer gatewayServer = new GatewayServer(new StackEntryPoint());
        gatewayServer.start();
        System.out.println("Gateway Server Started");
    }
 
}

分析一下代码中重要部分:

第一,声明了一个类,提供了一个对于预配置过的stack的访问

public Stack getStack() {
    return stack;
}

第二、创建一个主方法。这个主方法可以位于另一个类中。在主方法中要干的第一件事就是初始化一个GatewayServer和将其连接到一个实体指针。

 

public static void main(String[] args) {
GatewayServer gatewayServer = new GatewayServer(new StackEntryPoint());
        ……

            最后你要开始gateway,它能接收python请求:

gatewayServer.start();

 

Warning:有可能会碰到java.Net.BindException: Address already 异常。有两种可能的原因:你程序中有可能已经运行了另一个实例或者另一程序正在侦听25333端口。你可以通过一下方法换端口:

GatewayServer gatewayServer = new GatewayServer(new StackEntryPoint(), 25334);

2、            写Python程序

第一步,导入必要的Py4j类:

>>> from py4j.java_gateway import JavaGateway

第二步,初始化一个JavaGateway,默认为localhost,端口25333

>>> gateway = JavaGateway()

Warning:如果收到这样的错误:socket.error:[Errno111]Connectionrefused ,意味着没有JVM运行着。要检查一下java程序是不是任然在跑(running)

然后就可以从gateway对象中访问实例指针了。

>>> stack = gateway.entry_point.getStack()

stack变量中已经包含了一个stack,试试push或pop一些元素。例子如下:

>>> stack.push("First %s" % ('item'))
>>> stack.push("Second item")
>>> stack.pop()
u'Second item'
>>> stack.pop()
u'First item'
>>> stack.pop()
u'Initial Item'

现在stack应该已经空了,如果你还要pop可能发生以下的事情:

>>> stack.pop()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "py4j/java_gateway.py", line 346, in __call__
    return_value = get_return_value(answer, self.gateway_client, self.target_id, self.name)
  File "py4j/java_gateway.py", line 228, in get_return_value
    raise Py4JJavaError('An error occurred while calling %s%s%s' % (target_id, '.', name))
py4j.java_gateway.Py4JJavaError: An error occurred while calling o0.pop.
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
    at java.util.LinkedList.entry(LinkedList.java:382)
    at java.util.LinkedList.remove(LinkedList.java:374)
    at py4j.examples.Stack.pop(Stack.java:42)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:616)
    at py4j.reflection.MethodInvoker.invoke(MethodInvoker.java:119)
    at py4j.reflection.ReflectionEngine.invoke(ReflectionEngine.java:392)
    at py4j.Gateway.invoke(Gateway.java:255)
    at py4j.commands.AbstractCommand.invokeMethod(AbstractCommand.java:125)
    at py4j.commands.CallCommand.execute(CallCommand.java:81)
    at py4j.GatewayConnection.run(GatewayConnection.java:175)
    at java.lang.Thread.run(Thread.java:636)

 

3、            collection,help和constructor

接下来你可以用list做实验了,加入一个元素并获得stack的内部list。

>>> stack.push('First item')
>>> internal_list = stack.getInternalList()
>>> len(internal_list)
1
>>> internal_list[0]
u'First item'
>>> internal_list.append('Second item')
>>> internal_list
[u'First item', u'Second item']
>>> stack.getInternalList()
[u'First item', u'Second item']

你可以看到,在JVM中创建的list和python的list差不多,同样使用[ ]操作符,同样可以使用类似len和append方法。接下来看看切片。

>>> sliced_list = internal_list[0:1]
>>> sliced_list
[u'First item']
>>> sliced_list.append('Third item')
>>> sliced_list
[u'First item', u'Third item']
>>> internal_list
[u'First item', u'Second item']
>>> stack.getInternalList()
[u'First item', u'Second item']
>>> stack.pushAll(sliced_list)
>>> stack.getInternalList()
[u'Third item', u'First item', u'First item', u'Second item']

切片也差不多。但是sublist的切片方法在java中没实现,因为sublist返回的是一个视图,并不是拷贝,当原始list改变时,视图就无效了。

接下来,你也可以尝试着将一个list作为参数传入java,用的是pushAll方法。如果你传一个纯净的python list的话,你可能会得到如下异常:

>>> stack.pushAll(['Fourth item'])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "py4j/java_gateway.py", line 158, in __call__
    args_command = ''.join([get_command_part(arg) for arg in args])
  File "py4j/java_gateway.py", line 68, in get_command_part
    command_part = REFERENCE_TYPE + parameter.get_object_id()
AttributeError: 'list' object has no attribute 'get_object_id'
>>> stack.getInternalList()
[u'Third item', u'First item', u'First item', u'Second item']

Py4j在默认上是不支持将纯python list装换成java list的。

一个JavaGateway允许你列出某个对象中所有可用的成员

>>> gateway.help(stack)
Help on class Stack in package py4j.examples:
 
Stack {
|
|  Methods defined here:
|
|  getInternalList() : List
|
|  pop() : String
|
|  push(String) : void
|
|  pushAll(List) : void
|
|  ------------------------------------------------------------
|  Fields defined here:
|
|  ------------------------------------------------------------
|  Internal classes defined here:
|
}

你无须用一个实体指针来创建和访问对象。你可以使用jvm成员来调用cunstructors和静态成员。例子如下:

>>> java_list = gateway.jvm.java.util.ArrayList()
>>> java_list.append(214)
>>> java_list.append(120)
>>> gateway.jvm.java.util.Collections.sort(java_list)
>>> java_list
0 0
原创粉丝点击