python - 如何从 Julia 程序中调用 Python 函数?
问题描述
我使用 numpy、pandas、scikit-learn 在 Python 中编写了一些代码。是否可以从 Julia 程序中调用此 Python 代码?
解决方案
I think you can think to three different way to call Python code from Julia, in order from the most low level to the highest level they are:
Use the Foreign Funcall Interface as suggested by @madbird. However you will almost surelly not want to do this, as a package that exploits it, PyCall.jl, already exists;
Use the abovementioned PyCall.jl package to call any python code and/or wrap a python library (well.. "most".. "any" is a dangerous word). Details of this below;
As wrapping a Python library using PyCall is very easy, the most important Python libraries have been already wrapped in Julia, like Pandas in Pandas.jl, Scikit-learn in ScikitLearn.jl, etc. If you need them, just use the corresponding Julia package.
How to use PyCall.jl to call Python code and libraries.
Note: The following is an excerpt from my "Julia Quick Syntax Reference" book (Apress, 2019)
Julia ⇄ Python
The "standard" way to call Python code in Julia is to use the PyCall package. Some of its nice features are: (a) it can automatically download and install a local copy of Python, private to Julia, in order to avoid messing with version dependency from our "main" Python installation and provide a consistent environment in Linux, Windows and MacOS; (b) it provides automatic conversion between Julia and Python types; (c) it is very simple to use.
Concerning the first point, PyCall
by default install the "private" Python environment in Windows and MacOS while it will use the system default Python environment in Linux. +
We can override such behaviour with (from the Julia prompt) ENV["PYTHON"]="blank or /path/to/python"; using Pkg; Pkg.build("PyCall");
where, if the environmental variable is empty, PyCall
will install the "private" version of Python.
Given the vast amount of Python libraries, it is no wonder that PyCall
is one of the most common Julia packages.
Embedding Python code in a Julia program is similar to what we saw with C++, except that we don't need (for the most) to wonder about transforming data. We both define and call the Python functions with py"..."
, and in the function call we can use directly our Julia data:
using PyCall
py"""
def sumMyArgs (i, j):
return i+j
def getNElement (n):
a = [0,1,2,3,4,5,6,7,8,9]
return a[n]
"""
a = py"sumMyArgs"(3,4) # 7
b = py"sumMyArgs"([3,4],[5,6]) # [8,10]
typeof(b) # Array{Int64,1}
c = py"sumMyArgs"([3,4],5) # [8,9]
d = py"getNElement"(1) # 1
Note that we don't need to convert even for complex data like arrays, and the results are converted back to Julia types.
Type conversion is automatic for numeric, boolean, string, IO stream, date/period, and function types, along with tuples, arrays/lists, and dictionaries of these types. Other types are instead converted to the generic PyObject
type.
Note from the last line of the previous example that PyCall
doesn't attempt index conversion (Python arrays are 0-based while Julia ones are 1-based): calling the python getNElement()
function with "1" as argument will retrieve what in Python is the element "1" of the array.
Using a Python library is straightforward as well, as shown in the below example that use the ezodf module to create an OpenDocument spreadsheet (a wrapper of ezodf
for ODS documents - that internally use PyCall - already exists, OdsIO).
Before attempting to replicate the following code, please be sure that the ezodf
module is available to the Python environment you are using in Julia. If this is an independent environment, just follow the Python way to install packages (e.g. with pip
). If you are using the "private" Conda environment, you can use the Conda.jl package and type using Conda; Conda.add_channel("conda-forge"); Conda.add("ezodf")
.
const ez = pyimport("ezodf") # Equiv. of Python `import ezodf as ez`
destDoc = ez.newdoc(doctype="ods", filename="anOdsSheet.ods")
sheet = ez.Sheet("Sheet1", size=(10, 10))
destDoc.sheets.append(sheet)
dcell1 = get(sheet,(2,3)) # Equiv. of Python `dcell1 = sheet[(2,3)]`. This is cell "D3" !
dcell1.set_value("Hello")
get(sheet,"A9").set_value(10.5) # Equiv. of Python `sheet['A9'].set_value(10.5)`
destDoc.backup = false
destDoc.save()
The usage in Julia of the module follows the Python API with few syntax differences.
The module is imported and assigned to a shorter alias, ez
.
We can then directly call its functions with the usual Python syntax module.function()
.
The doc
object returned by newdoc
is a generic PyObject
type. We can then access its attributes and methods with myPyObject.attribute
and myPyObject.method()
respectively.
In the cases where we can't directly access some indicized values, like sheet[(2,3)]
(where the index is a tuple) we can invoke instead the get(object,key)
function. +
Finally, note again that index conversion is not automatically implemented: when asking for get(sheet,(2,3))
these are interpreted as Python-based indexes, and cell D3
of the spreadsheet is returned, not B2
.
推荐阅读
- reactjs - 将redux与react一起使用,为什么获取新数据会删除我以前状态的一部分?
- apache-spark - Hadoop YARN Cluster / Spark and RAM Disks
- android - RecyclerView + MediaPlayer + 切换按钮 + 字符串 Uri
- php - How result is bind in UNION prepared statement in Mysqli?
- python - Changing the last found target value in a linked list (node chain)
- javascript - Arrow function's lexical scope not behaving as expected
- ios - Swift 4.2 语法更改 - Swift 类型推断发生了什么?
- r - Calculate First Quartile of a dataset using data.frame() where row numbers have been removed in R
- java - Xamarin spinner firing itemSelected event multiple times
- c# - MVC5 Microsoft.Owin.Twitter 403 Forbidden error