在Julia语言中调用Python函数

时间:2014-10-30 17:00:13   收藏:0   阅读:719


在PyCall扩展包中,模仿Python的import语句,提供了一个可以导入Python模块的@pyimport宏。并且,为能在Julia中使用模块内的函数和常量做了封装,以及支持在Julia与Python间的自动类型转换。

同时,它还提供了对Python对象进行底层操作的设施。其中包括能与不透明的Python对象相对应的‘PyObjec‘类型,以及在Julia语言中对Python函数进行调用且做类型转换的pycall

安装

在Julia中,只需要使用Pkg.add("PyCall"),就可以通过包管理进行安装。需要你安装Julia0.2及之后的版本。
最新的开发版本可以从https://github.com/stevengj/PyCall.jl取得。如果你想要在安装之后再做版本切换,你可以cd ~/.julia/PyCall,然后git pull git://github.com/stevengj/PyCall.jl master

使用

下面是一个简单的调用Python中math.sin的例子,并和Julia内建的sin进行了比较:

using PyCall
@pyimport math
math.sin(math.pi / 4) - sin(pi / 4) # returns 0.0

数值、布尔、字符串、IO stream、函数、元组、数组或列表、以及包含这些类型的字典等,它们都会自动进行类型的转换(Python函数会被转换或传递为Julia的函数,反之亦然)。其它类型则是通过通用的PyObject提供的。

Python编写的子模块必须通过单独@pyimport导入,并且必须提供一个标识符,以使其能在Julia中使用。例如:

@pyimport numpy.random as nr
nr.rand(3,4)

上例中,Julia利用了从Python的Numpy数组API转换过来的多维数组。从Julia向Python传递数据默认不做拷贝。而从Python编程接口取得数据,则取得的只是拷贝。Python到Julia数组间的无拷贝转换可以通过下面的PyArray类型实现。

关键字参数也可以在两者间传递。例如,matplotlib的pyplot使用关键字参数描述绘图的选项,并且这个功能可以在Julia中通过下面的方式访问:

@pyimport matplotlib.pyplot as plt
x = linspace(0,2*pi,1000); y = sin(3*x + 4*cos(2*x));
plt.plot(x, y, color="red", linewidth=2.0, linestyle="--")
plt.show()

尽管如此,为了更好地与绘图终端集成,且为了避免使用一次show函数,建议使用matplotlib时调用Julia的PyPlot模块

任意的Julia函数都可以作为参数传递给Python的某个子程序。例如,为找到cos(x) - x的根,我们可以从scipy.optimize调用牛顿求解器:

@pyimport scipy.optimize as so
so.newton(x -> cos(x) - x, 1)

在Julia中导入的Python模块的最大不同是,对象的属性(或成员)的访问要通过 o[:attribute],而不是o.attribute。并且,你需要使用get(o, key),而不能使用o[key]。这是因为Julia还不允许对 . 操作符进行重载。可以在下面PyObject小节查看这方面的内容。此外,pywrap函数提供了创建匿名模块来模仿.访问方式(这其实和@pyimport的作用相同)。例如,我们可以像下面这样使用Biopython

@pyimport Bio.Seq as s
@pyimport Bio.Alphabet as a
my_dna = s.Seq("AGTACACTGGT", a.generic_dna)
my_dna[:find]("ACT")

而在Python中,最后一步应该是my_dna.find("ACT")

排除故障

这里有一些常见问题的解决办法:

Python对象编程接口

在Julia中,@pyimport宏通过下面的PyObject,在许多子程序之上建立了对Python对象的操作。这些子程序可以用来对Julia和Python之间传递的类型和数据进行强大的操作,以及访问Python其它的功能(特别是稍后将提到的协程的底层接口)。

类型

PyObject

PyCall模块也提供了一个代表Python对象引用的新类型PyObject,即对Python的C API的一个封装。

PyObject(o)为Julia的一些类型提供了构造函数。相应地,PyCall同时也提供了convert(T, o::PyObject)来将PyObjects转换为一个Julia的类型T。目前,支持的类型有:数值(整型、实数和复数)、布尔型、字符串、函数,以及它们组成的元组、数组或序列。正在计划提供更多的支持(Julia符号被转换成Python字符串)。

o::PyObjecto[:attribute]与Python中的o.attribute是等同的,并做了自动类型转换。如果想要在取得一个PyObject的属性时不做类型转换,则采用o["attribute"]代替。

o::PyObjectget(o, key)与Python中的o[key]是等同的,并做了自动类型转换。如果想要像PyObject一样做get操作,并不做类型转换,则使用get(o, PyObject, key)代替,或者使用更为通用的get(o, SomeType, key)
如果你想在找不到关键字的时候提供一个默认值,则可以调用get(o, key, default)或者get(o, SomeType, key, default)
类似地,set!(o, key, val)等同于Python中的o[key] = valdelete!(o, key)等同于Python中的del o[key]

PyArray

支持将Numpy的多维数组(ndarray)转换为Julia的Array类型。不过,转换之后使用的是数据的拷贝。

另外,PyCall模块提供了一个新的类型PyArray(是AbstractArray的一个子类),它实现了对一个NumPy数组的非拷贝封装(目前仅支持对包含数值类型和对象类型的数组)。使用方法是,对于返回ndarraypycall,则使用PyArray作为返回值的类型。对于一个ndarray对象,则对其调用PyArray(o::PyObject)进行转换。从技术上讲,PyArray可以对任何的使用Numpy数组接口提供数据指针和形状信息的Python对象使用。

按照惯例,当向Python传递数组的时候,Julia的Array类型会被转换为PyObject类型,而且不会通过NumPy创建一个拷贝。比如Julia的Array作为pycall的参数传递时就是这样。

PyVector

PyCall模块提供了一个新的类型PyVector(是AbstractVector的一个子类),它实现了对任意Python列表或序列对象的非拷贝封装。与PyArray不同,PyVector类型不仅限于对NumPy数组使用(尽管相对于PyVector来说,PyArray通常效率更高)。使用方法是,将使用PyArray作为返回列表或序列对象(包括元组)的pycall的返回值类型。或者,对一个序列的对象o调用PyVector(o::PyObject)

v::PyVector(即PyVector类型的变量v)支持通过v[index]对元素进行引用和分配,以及配合delete!pop!进行操作。copy(v)可以将v转换为一个普通的Julia Vector

PyDict

PyCall模块同时提供了一个新的类型PyDict(是Association的一个子类),它实现了对任意Python字典对象(或者任何一个实现了mapping协议的对象)的非拷贝封装,使用上与PyVector类似。使用方法是,将PyDict作为返回字典的pycall的返回值的类型。或者对一个字典对象o调用PyDict(o::PyObject)PyDict默认是一个自动根据运行时所给参数的类型进行构造的Any => Any的字典(或许实际上是PyAny => PyAny,当然还有可能是其他,比如PyDict{Int32,ASCIIString})。但是,如果你已经确切地知道了所要创建字典的参数类型,那么你可以选择使用PyDict{K,V},固定构造器参数KV的类型来创建。

目前,想Python传递一个Julia字典,将会创建一个Julia字典的拷贝。

PyTextIO

Julia的IOstreams会被转换成实现了RawIOBase接口的Python对象。在Python中,RawIOBase可以用来进行二进制读取和输出。尽管如此,有些Python代码(特别是unpickling的代码)还是期望一个stream能够实现TextIOBase接口。它与RawIOBase主要的不同是readreadall函数返回的是字符串而不是字节数组。如果你要向一个text-IO对象传递IO stream,则调用PyTextIO(io::IO)进行转换。
目前,尚且没有好的办法可以让Python在接收stream参数后,自动地确定是返回字符串还是二进制的数据。并且,与Python不同,Julia打开文件的时候不会单独地区分"text"或"binary"模式。所以,我们无法简单地从文件打开的方式来确定转换方式。

PyAny

在进行类型转换的时候,PyAny类型可以告诉PyCall通过在运行时侦测Python数据类型,然后将其转换为Julia的本地类型。也就是说,pycall(func, PyAny, ...)convert(PyAny, o::PyObject)是自动将运行的结果转换为Julia的类型(如果可以转换合法的话)。不过,有的时候这样虽然很方便,但是可能会降低一些程序的性能(这是由于运行时类型检查存在开销,以及Julia JIT编译器不能作类型推断)。

调用Python

在大多数时候,@pyimport都可以在运行时对Python类型进行检测,并自动地将其转换为合适的Julia类型。尽管如此,通过下面更底层的函数可以进行更强大的类型转换控制。比如,使用非拷贝的PyArray来转换Python多维数组,而不是将其拷贝至Array
在已确切知晓Python返回类型的情况下使用pycall,将有助于提升程序的性能。因为这样可以减少运行时类型推断的开销,同时可以为Julia编译器提供更多的类型信息。

初始化

当你调用任何高层的PyCall子程序时,Python解释器(与名为python的可执行程序相对应)就会默认地进行初始化,并在退出Julia前,一直留在内存中。
尽管如此,你或许想要改变这些默认的行为。比如改变默认的Python解释器的版本,通过ccall直接调用底层函数,或者想要释放Python消耗的内存。那么,可以采用以下的方式办到:

GUI事件循环

对于有GUI的Python包,特别是像matplotlib (或者MayaVi、Chaco)这样的绘图包,可以非常方便地在Julia中以异步任务的方式启动一个GUI事件循环,比如鼠标点击事件。所以,GUI响应时并没有阻止Julia的输入提示符。PyCall包含了实现一些常用跨平台GUI工具的事件循环的函数。这些工具的Python模块有:wxWidgetsGTK+,via the PyQt4或者PySide

你可以用以下方式设置一个GUI事件循环:

在使用提供GUI的python库时,其实在导入之前,就已经可以很方便地启动一个GUI工具的事件循环。但是,有的时候还是需要显式地指定使用的是哪一个库,以及哪种交互模式。为了让这更加简单,可以使用一些对流行Python库做封装的Julia模块,比如Julia的PyPlot 模块

访问Python的底层API

If you want to call low-level functions in the Python C API, you can
do so using ccall. Just remember to call pyinitialize() first, and:
如果想要调用Python底层的C API,你可以使用ccall。不过,要记住首先得调用pyinitialize(),并且:

作者

这个扩展包是由Steven G. Johnson编写.

翻译至PyCall的github文档https://github.com/stevengj/PyCall.jl,转载请注明出处。

原文:http://blog.csdn.net/adousen/article/details/40622699

评论(0
© 2014 bubuko.com 版权所有 - 联系我们:wmxa8@hotmail.com
打开技术之扣,分享程序人生!