Skip to content

Commit 579ec1c

Browse files
author
Christopher Doris
committed
adds PYCALL option
1 parent c2d146f commit 579ec1c

File tree

3 files changed

+37
-7
lines changed

3 files changed

+37
-7
lines changed

docs/src/getting-started.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,15 @@ packages can always `import juliacall`.
3838

3939
If Julia and Python are in your PATH, then no further set-up is required.
4040
Otherwise, the following environment variables control how the package finds these.
41-
- `JULIA_PYTHONCALL_EXE`: Path to the Python executable. Or the special value `CONDA` which uses
42-
Python from the default conda environment, or `CONDA:{env}` to use the given environment.
43-
In this case, if `conda` is not detected then `Conda.jl` will automatically install
44-
[`miniconda`](https://docs.conda.io/en/latest/miniconda.html) in your Julia depot.
41+
- `JULIA_PYTHONCALL_EXE`: Path to the Python executable. Or one of the following special
42+
values:
43+
- `CONDA`: Use Python from the default conda environment. In this case, if `conda` is not
44+
detected then `Conda.jl` will automatically install
45+
[`miniconda`](https://docs.conda.io/en/latest/miniconda.html) in your Julia depot.
46+
- `CONDA:{env}`: Use Python from the given conda environment. Also automatically installs
47+
miniconda.
48+
- `PYCALL`: Import [`PyCall`](https://github.com/JuliaPy/PyCall.jl) and use whichever
49+
Python that uses.
4550
- `JULIA_PYTHONCALL_LIB`: Path to the Python library. Normally this is inferred from the Python
4651
executable, but can be over-ridden.
4752
- `PYTHON_JULIACALL_EXE`: Path to the Julia executable.

docs/src/pycall.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
# Comparison to PyCall
22

3-
The existing package [`PyCall`](https://github.com/JuliaPy/PyCall.jl) is another similar interface to Python. Here is a comparison of the designs:
3+
The existing package [`PyCall`](https://github.com/JuliaPy/PyCall.jl) is another similar interface to Python.
4+
5+
You can use both `PythonCall` and `PyCall` in the same session, provided they are both using the same Python library.
6+
One way to ensure this is to set `JULIA_PYTHONCALL_EXE=PYCALL` (see [Environment variables](@ref)).
7+
8+
Here is a comparison of the designs:
49
- **Flexibility of conversion.** The mechanisms for data conversion from Python to Julia are different. In `PyCall`, conversion to `T` (via `convert(T,::PyObject)`) essentially only takes `T` into account, so for example when `T=Real` then the input will always be converted to a Python `float`, which is then converted to a `Cdouble`. In `PythonCall`, conversion takes into account both the target type `T` 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 in `PythonCall`, converting to a `Real` might return an `Int` (e.g. the input is a `int`), or `Cdouble` (e.g. the input is `float`), or `Rational{BigInt}`, or...
510
- **Lossiness of conversion from Python.** In `PyCall`, the default `PyAny` 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 a `Vector{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, since `Vector` and `list` have different indexing semantics. In `PythonCall`, the default conversion is to `PyObject` (non-lossy), and even if you convert to `Any` then by default this will be non-lossy: for example a `list` will be converted to a `PyList` which is a `Vector`-like view of the list.
611
- **Lossiness of conversion to Python.** Similarly, in `PyCall` the default conversion from Julia to Python can be lossy: a `Vector{Int}` will be converted to a `list` of `int`s for example, losing mutability of the original vector. In `PythonCall`, only immutable values are truly converted to Python, everything else is wrapped into a Python wrapper around the Julia value: a `Vector{Int}` is wrapped into a `juliacall.VectorValue` which is a `list`-like sequence type

src/init.jl

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
const PYCALL_UUID = Base.UUID("438e738f-606a-5dbb-bf0a-cddfbfd45ab0")
2+
const PYCALL_PKGID = Base.PkgId(PYCALL_UUID, "PyCall")
3+
14
check_libpath(PyCall) = begin
25
if realpath(PyCall.libpython) == realpath(CONFIG.libpath)
36
# @info "libpython path agrees between PythonCall and PyCall" PythonCall.CONFIG.libpath PyCall.libpython
@@ -16,6 +19,23 @@ end
1619
# Check Python is initialized
1720
C.Py_IsInitialized() == 0 && error("Python is not already initialized.")
1821
CONFIG.isinitialized = CONFIG.preinitialized = true
22+
elseif get(ENV, "JULIA_PYTHONCALL_EXE", "") == "PYCALL"
23+
# Import PyCall and use its choices for libpython
24+
PyCall = get(Base.loaded_modules, PYCALL_PKGID, nothing)
25+
if PyCall === nothing
26+
PyCall = Base.require(PYCALL_PKGID)
27+
end
28+
CONFIG.exepath = PyCall.python
29+
CONFIG.libpath = PyCall.libpython
30+
CONFIG.libptr = dlopen_e(CONFIG.libpath, CONFIG.dlopenflags)
31+
if CONFIG.libptr == C_NULL
32+
error("Python library $(repr(CONFIG.libpath)) (from PyCall) could not be opened.")
33+
end
34+
CONFIG.pyprogname = PyCall.pyprogramname
35+
CONFIG.pyhome = PyCall.PYTHONHOME
36+
# Check Python is initialized
37+
C.Py_IsInitialized() == 0 && error("Python is not already initialized.")
38+
CONFIG.isinitialized = CONFIG.preinitialized = true
1939
else
2040
# Find Python executable
2141
exepath = something(
@@ -52,7 +72,7 @@ end
5272
5373
Ensure either:
5474
- python3 or python is in your PATH
55-
- JULIA_PYTHONCALL_EXE is "CONDA" or "CONDA:<env>"
75+
- JULIA_PYTHONCALL_EXE is "CONDA", "CONDA:<env>" or "PYCALL"
5676
- JULIA_PYTHONCALL_EXE is the path to the Python executable
5777
""")
5878
end
@@ -96,7 +116,7 @@ end
96116
end
97117

98118
# Compare libpath with PyCall
99-
PyCall = get(Base.loaded_modules, Base.PkgId(Base.UUID("438e738f-606a-5dbb-bf0a-cddfbfd45ab0"), "PyCall"), nothing)
119+
PyCall = get(Base.loaded_modules, PYCALL_PKGID, nothing)
100120
if PyCall === nothing
101121
@require PyCall="438e738f-606a-5dbb-bf0a-cddfbfd45ab0" check_libpath(PyCall)
102122
else

0 commit comments

Comments
 (0)