Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to call a Python function from a Julia program?

Tags:

I've written some code in Python using numpy, pandas, scikit-learn. Is it possible to call this Python code from a Julia Program?

like image 488
Thiago Alexandre Avatar asked Mar 16 '21 03:03

Thiago Alexandre


1 Answers

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:

  1. 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;

  2. 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;

  3. 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.

Embed Python code in a Julia program

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.

Use Python libraries

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.

like image 91
Antonello Avatar answered Oct 22 '22 10:10

Antonello