Comparison to PyCall
The existing package PyCall
is another similar interface to Python.
You can use both PythonCall
and PyCall
in the same session, provided they are both using the same Python library. One way to ensure this is to set JULIA_PYTHONCALL_EXE=PYCALL
(see Environment variables).
Here is a comparison of the designs:
- Flexibility of conversion. The mechanisms for data conversion from Python to Julia are different. In
PyCall
, conversion toT
(viaconvert(T,::PyObject)
) essentially only takesT
into account, so for example whenT=Real
then the input will always be converted to a Pythonfloat
, which is then converted to aCdouble
. InPythonCall
, conversion takes into account both the target typeT
and the Python type of the Python object, and an extensible system allows one to declare conversions for any combination. Many conversions for overlapping combinations can be defined and the most specific one takes precedence. Hence inPythonCall
, converting to aReal
might return anInt
(e.g. the input is aint
), orCdouble
(e.g. the input isfloat
), orRational{BigInt}
, or... - Lossiness of conversion from Python. In
PyCall
, the defaultPyAny
conversion from Python to Julia can be lossy in the sense that it is impossible to recover the original value exactly. For example a list of ints is converted to aVector{Int}
which is a copy of the data, and therefore modifying the original list is not possible. It is also a source of off-by-one errors, sinceVector
andlist
have different indexing semantics. InPythonCall
, the default conversion is toPyObject
(non-lossy), and even if you convert toAny
then by default this will be non-lossy: for example alist
will be converted to aPyList
which is aVector
-like view of the list. - Lossiness of conversion to Python. Similarly, in
PyCall
the default conversion from Julia to Python can be lossy: aVector{Int}
will be converted to alist
ofint
s for example, losing mutability of the original vector. InPythonCall
, only immutable values are truly converted to Python, everything else is wrapped into a Python wrapper around the Julia value: aVector{Int}
is wrapped into ajuliacall.VectorValue
which is alist
-like sequence type - Automatic conversion. In
PyCall
, most function calls, attribute accesses, indexing, etc. of Python objects by default automatically convert their result to a Julia type. InPythonCall
the default is to always returnPyObject
. The latter behaviour provides type-stability. It also makes interacting with Python values more predictable and allows generic programming (where the type of the result is not known). It also allows the user to pick another type to convert to after the fact, whereas sincePyCall
conversion can be lossy, this is sometimes not possible there. - Building.
PyCall
locates libpython in its build step, so that it is aconst
in the module code. This makesccall
s and the like straightforward.PythonCall
does this at run-time, which slightly complicates the code (although it is abstracted away) but means that the module does not need to be rebulit for different Python versions. - Default Python. By default
PyCall
uses the version of Python inconda
and will silently installminiconda
for you if it doesn't exist.PythonCall
by default simply uses the version of Python in the PATH. Both are customizable through environment variables. - Python modules.
PyCall
has a companion Python modulejulia
for calling Julia from Python. So doesPythonCall
, but calledjuliacall
. Both of them usePyCall
/PythonCall
under the hood on the Julia side. ThePyCall
one is itself about as complex in implementation asPyCall
. ThePythonCall
one is about 50 lines of code (essentially just finding and loading libjulia and the PythonCall module) and provides a single simple entrypoint: the juliaMain
module. - Compatability.
PyCall
supports Julia 0.7+ and Python 2.7+, whereasPythonCall
supports Julia 1.0+ and Python 3.5+.PyCall
requiresnumpy
to be installed,PythonCall
doesn't (it provides the same fast array access through the buffer protocol and array interface). - Startup time.
PythonCall
takes longer to start thanPyCall
, largely because there are a lot of wrapper types (juliacall.AnyValue
etc.) to compile.