搞了两三个礼拜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解释器里一样被调用,java collection也可以通过标准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
[120, 214]
关于Py4j的调研就到此为止吧,希望能用于Django,如果能用的话,就将其余的文档翻译一下。