# \[ANN\] PythonCall and JuliaCall

**URL:** <https://discourse.julialang.org/t/ann-pythoncall-and-juliacall/76778>\
**Category:** Package Announcements\
**Tags:** interoperability, python, pythoncall\
**Created:** [February 19, 2022, 10:49pm UTC](https://discourse.julialang.org/t/ann-pythoncall-and-juliacall/76778 "2022-02-19T22:49:15Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![cjdoris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cjdoris/32/213133_2.png) [@cjdoris](https://discourse.julialang.org/u/cjdoris)\
**Post date:** [February 19, 2022, 10:49pm UTC](https://discourse.julialang.org/t/ann-pythoncall-and-juliacall/76778/1 "2022-02-19T22:49:15Z")

</div>

# [PythonCall & JuliaCall](https://github.com/cjdoris/PythonCall.jl)

I’m very pleased to finally announce these packages on here. They have existed for quite some time, but I’m now happy to encourage more users to try them out.

**PythonCall** is a Julia package to interoperate with Python, so for example you can do:

```julia
using PythonCall
plt = pyimport("matplotlib.pyplot")
plt.plot(randn(100))

```

It can be installed with `pkg> add PythonCall`.

**JuliaCall** is a corresponding Python package to interoperate with Julia, so for example:

```python
from juliacall import Main as jl
import numpy as np
jl.seval("using Plots")
jl.plot(np.random.randn(100))

```

It can be installed with `pip install juliacall`.

In what follows, I’ll introduce some of the cool features of these packages. Most of you will be aware of the similar packages [PyCall](https://github.com/JuliaPy/PyCall.jl) and [PyJulia](https://github.com/JuliaPy/pyjulia), which have existed much longer, so there will inevitably be some comparison with these.

## Extensible multimedia support

PythonCall knows how to display anything that IPython knows how to display (via `_repr_mimebundle_` and friends), and PyCall can too. Unlike PyCall, it can also display Matplotlib figures, and what’s more you can add more display rules.

Hence in Pluto you can do

```julia-auto
plt.plot(rand(100))
plt.gcf()

```

and the plot (returned by `gcf`) is displayed.

On the other side, JuliaCall knows how to display Julia objects in IPython.

## Flexible and extensible conversions

PythonCall has a very flexible function `pyconvert(T, x)` which converts the Python object `x` to a Julia object of type `T`. Unlike PyCall (which similarly has `convert(T, x)`) this function can take in to account both the Python `type(x)` and the Julia type `T`, which means a richer set of conversions are possible.

For example, in PyCall if you call `convert(Vector{UInt8}, x)` where `x` is a Python `list` of `int`, it will fail because it is expecting a `bytes`. In PythonCall `pyconvert(Vector{UInt8}, x)` will succeed either way: it has rules for `list` and `bytes` and selects the most specific conversion rule applicable to the inputs.

You can even do something like `pyconvert(Vector{<:Real}, x)` and automatically get back a `Vector{Int}` or `Vector{Float64}` or whatever is appropriate for the items in `x`.

This system is extensible, so packages can add more rules for different `(T, type(x))` pairs.

## Predictable syntax

PyCall has some behaviours making it hard to predict how it behaves.

For example `x[0]` does not do what you think! Firstly, it gives a deprecation warning because `get(x, 0)` is the proper PyCall syntax for indexing. Secondly, it actually gets the item at index `-1`, which is supposed to be a convenience to compensate for Python indexing being 0-up and Julia being 1-up. But if for example `x` is a `dict` then this is not what you want (I guess that’s why it is deprecated).

In PythonCall, `x[0]` just gets the item at index `0` no matter what `x` is.

For another example, PyCall eagerly converts results to Julia objects. This means that `sys.path.append("/some/path")` will not work because `sys.path` is immediately converted to a `Vector`. You might try `push!(sys.path, "/some/path")` but since the `Vector` is a copy of `sys.path` it does not actually mutate the original `sys.path`. To overcome this, PyCall has the syntax `sys."path"."append"("/some/path")` to prevent this eager conversion.

In PythonCall, `sys.path.append("/some/path")` does exactly what you intend. This is because most operations on Python objects return Python objects instead of converting them. If you actually need to convert anything to Julia you can use `pyconvert`.

As a side-effect, operations in PythonCall are type-inferrable (they mostly return `Py`) whereas operations in PyCall are not (they can return anything) so PyCall code can be type-unstable if you are not careful.

## Non-copying conversions

By default, any mutable objects passed between Python and Julia are converted without copying any data - that is they lazily wrap the original object. This makes conversion super fast for large containers, and means that if the converted container is mutated, then the changes also appear on the original object.

A small number of immutable types (such as booleans, numbers, strings and tuples) are converted to the native types, e.g. a Julia `Int64` becomes a Python `int`.

In the Julia-to-Python direction, Julia objects are wrapped as a `juliacall.AnyValue`. Some objects are wrapped to a subtype of this. For example any `AbstractVector` is wrapped as a `juliacall.VectorValue` which satisfies the sequence interface and behaves pretty much like a `list`:

```julia
x = [1,2,3] # a Julia vector
y = Py(x) # wrap as a juliacall.VectorValue
y.append(4) # mutating y also mutates x
println(x) # [1, 2, 3, 4]

```

If you actually want a list you can do `pylist([1,2,3])`.

In the Python-to-Julia direction, mutable Python objects are typically left as Python objects. Again, some objects are wrapped differently:

```julia
x = pylist([1,2,3]) # a Python list
y = pyconvert(AbstractArray, x) # wrap as a PyList{Int}
push!(y, 4) # mutating y also mutates x
println(x) # [1, 2, 3, 4]

```

## Array conversion

Particularly of note is that if `x` is a strided Julia array then it will be wrapped in Python to a `juliacall.ArrayValue` which satisfies the buffer protocol and Numpy array interface. This means that a `Vector{UInt8}` can be passed to any function expecting a `bytes`-like object, and a `Vector{Float64}` can be passed to any function expecting a Numpy-array-like object. In particular `numpy.array(x)` will convert it to an actual Numpy array.

In the other direction, if `x` is a `bytes` or `numpy.ndarray` (or anything satisfying the buffer protocol or array interface) then `PyArray(x)` gives an `AbstractArray` view of the data.

## Tabular data

If `x` is a Julia table (in the Tables.jl sense) then `pytable(x)` will convert it to a Pandas dataframe. You can ask for other output formats, such as `dict` of `list`.

If `x` is a Python table (for now only Pandas dataframes are supported) then `PyTable(x)` wraps it as a Julia table.

## Isolated dependencies

All the Python dependencies for PythonCall are (by default) managed by the [CondaPkg](https://github.com/cjdoris/CondaPkg) which I have [announced separately](https://discourse.julialang.org/t/ann-condapkg-jl-v0-2-6-now-with-repl-integration/75370).

If your project needs Numpy you can simply do

`pkg> conda add numpy`

before loading PythonCall. Then a Conda environment is created containing Numpy. This environment is specific to your Julia project, so dependencies are totally isolated between projects.

This also creates a `CondaPkg.toml` file recording the dependencies (analogous to `Project.toml`) so if you save it to your package, then any users of the package also get these dependencies installed.

JuliaCall similarly uses a new package [JuliaPkg](https://github.com/cjdoris/pyjuliapkg) to manage its dependencies. If you are using a Python virtual environment or Conda environment, then a Julia project specific to that is used, again keeping dependencies totally isolated.

In all cases, Python or Julia are automatically installed if needed, meaning that packages depending on PythonCall or JuliaCall can be used with zero set-up. They are installed to an environment-specific location, so that removing the environment also removes any dependencies.

## Use different Pythons without rebuilding

PyCall currently hard-codes the path to libpython in its build step. This means that if you need to use different versions of Python, then you need to rebuild PyCall each time you switch.

PythonCall has no build step. You can start multiple Julia sessions in multiple projects each requiring a different version of Python, and PythonCall will work fine in all of them.

## JuliaCall is teeny

It pretty much consists of [this one file](https://github.com/cjdoris/PythonCall.jl/blob/main/python/juliacall/ __init__.py) just 137 lines long. This is because most of the implementation is in PythonCall, and all JuliaCall needs to do is find Julia and get it to import PythonCall.

The relevance of this is that you get a very consistent experience between PythonCall and JuliaCall. All the conversions work the same in both directions from either package. Since JuliaCall is bundled into PythonCall, any Python package can do `import juliacall` and it will work properly regardless of whether it is running in Python itself or from PythonCall in Julia.

---

<div class="post-metadata">

**Author:** ![photor](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/photor/32/14343_2.png) [@photor](https://discourse.julialang.org/u/photor)\
**Post date:** [February 20, 2022, 2:44am UTC](https://discourse.julialang.org/t/ann-pythoncall-and-juliacall/76778/2 "2022-02-20T02:44:04Z")

</div>

Excuse me, what’s the difference from the package PyCall?

---

<div class="post-metadata">

**Author:** ![ufechner7](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ufechner7/32/51363_2.png) [@ufechner7](https://discourse.julialang.org/u/ufechner7)\
**Post date:** [February 20, 2022, 3:20am UTC](https://discourse.julialang.org/t/ann-pythoncall-and-juliacall/76778/3 "2022-02-20T03:20:10Z")

</div>

The existing package [PyCall](https://github.com/JuliaPy/PyCall.jl) is another similar interface to Python. Here we note some key differences, but a more detailed comparison is in the documentation.

- PythonCall supports a wider range of conversions between Julia and Python, and the conversion mechanism is extensible.
- PythonCall by default never copies mutable objects when converting, but instead directly wraps the mutable object. This means that modifying the converted object modifies the original, and conversion is faster.
- PythonCall does not usually automatically convert results to Julia values, but leaves them as Python objects. This makes it easier to do Pythonic things with these objects (e.g. accessing methods) and is type-stable.
- PythonCall installs dependencies into a separate conda environment for each Julia project. This means each Julia project can have an isolated set of Python dependencies.
- PythonCall supports Julia 1.4+ and Python 3.5+ whereas PyCall supports Julia 0.7+ and Python 2.7+.

---

<div class="post-metadata">

**Author:** ![tk3369](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tk3369/32/2824_2.png) [@tk3369](https://discourse.julialang.org/u/tk3369)\
**Post date:** [February 20, 2022, 5:02am UTC](https://discourse.julialang.org/t/ann-pythoncall-and-juliacall/76778/4 "2022-02-20T05:02:16Z")

</div>

This post reads like PythonCall has more (or better) functionality than PyCall. That’s great! 👍

Is there anything that PyCall can do today while PythonCall cannot (yet). What would you suggest to anyone who might be itching to switch over to PythonCall?

---

<div class="post-metadata">

**Author:** ![jlapeyre](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jlapeyre/32/4514_2.png) [@jlapeyre](https://discourse.julialang.org/u/jlapeyre)\
**Post date:** [February 20, 2022, 5:58am UTC](https://discourse.julialang.org/t/ann-pythoncall-and-juliacall/76778/5 "2022-02-20T05:58:50Z")

</div>

- Calling Julia from Python, `juliacall` seems to have smaller overhead (in time) than `julia`:

```python
In [5]: %timeit julia.Main.identity(1)
272 µs ± 4.29 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

In [6]: jid = julia.Main.identity

In [7]: %timeit jid(1)
3.16 µs ± 249 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

```

```python
In [6]: %timeit juliacall.Main.identity(1)
3.01 µs ± 197 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [7]: jid = juliacall.Main.identity

In [8]: %timeit jid(1)
1.38 µs ± 21.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

```

- In the Python module `julia`, Julia modules are wrapped in a subclass of the Python `Module` type. They are imported using the Python import syntax: `from julia import Example`. On the other hand `juliacall` has a python type hierarchy of wrappers for Juia objects. So `isinstance(julia.Main, type(sys))` is `True`. And `isinstance(juliacall.Main, juliacall.ModuleValue)` and `isinstance(juliacall.Main, juliacall.AnyValue)` are both `True`. I don’t have enough experience to evaluate which might be better. It would be interesting to hear the arguments.

- The `julia` Python module (from `pyjulia`-`PyCall`) allows you to specify a system image for Julia. But, the author of `PythonCall` has plans to add support for this.

- After importing `julia`, you must explicitly initialize the Julia process. (Kind of; importing a julia module also initializes the Julia process). So `import julia`, just loads the code. Then `from julia import Main` finds `libjulia`, initializes the julia runtime, etc. There is also an API to control how initialization is done. Also, `julia` doesn’t do much work to find the julia executable and libjulia. On the other hand `juliacall` does everything upon `import juliacall`: looks for Julia, downloads it if needed, calls the init function in `libjulia`, etc. Recently, the author added a facility for a package author to stop this initialization with an environment variable. I have not yet had a chance to try this. But, if it works as I understand, then both packages allow you to customize the initialization.

- Related to the above; `juliacall` is more of a one-stop shop for writing Python packages that use Julia than is `julia` (and I think the same is true for the other direction, ie. using PythonCall) . This is probably in part why you are given a lot of freedom in the startup of `julia`— because you have more need for it. In addition to examples above, it does not manage Julia packages (and PyCall does not manage python packages). Some of the capabilities of `juliacall` and `PythonCall` have been devolved to other packages, such as those mentioned in the OP. But, still the ecosystem intends to offer much more for specifying your environment and packages. This is done mostly through configuration files.

---

<div class="post-metadata">

**Author:** ![TsurHerman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tsurherman/32/1234_2.png) [@TsurHerman](https://discourse.julialang.org/u/TsurHerman)\
**Post date:** [February 20, 2022, 7:15am UTC](https://discourse.julialang.org/t/ann-pythoncall-and-juliacall/76778/6 "2022-02-20T07:15:03Z")

</div>

If you have an encapsulated project that has its own python dependencies (for example a specific PyTorch version corresponding to a specific python version) then PythonCall is a nicer solution with less mysterious stuff, and a neat place to place all dependencies in a human-readable TOML file.

Makes deploying your work to the cloud or another remote computer easier. All python dependencies you need are now managed by the project itself

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [February 20, 2022, 7:28am UTC](https://discourse.julialang.org/t/ann-pythoncall-and-juliacall/76778/7 "2022-02-20T07:28:04Z")

</div>

Wow, that’s seriously great!

As a heavy user of PyCall/PyPlots, I wonder:

PythonCall already displays matplotlib plots, so it doesn’t need a `PyPlots.jl` equivalent at all? Do you know of any remaining advantages of `PyCall + PyPlots` vs `PythonCall`?

Currently, I have PyCall’s `~/.julia/conda` taking about 2.5 Gb of space. Would `PythonCall` duplicate all common python dependencies (numpy, scipy, matplotlib, mkl, …) for each Julia env? Or something more clever happens?

---

<div class="post-metadata">

**Author:** ![cjdoris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cjdoris/32/213133_2.png) [@cjdoris](https://discourse.julialang.org/u/cjdoris)\
**Post date:** [February 20, 2022, 2:00pm UTC](https://discourse.julialang.org/t/ann-pythoncall-and-juliacall/76778/8 "2022-02-20T14:00:24Z")

</div>

> [@tk3369](#):
>
> Is there anything that PyCall can do today while PythonCall cannot (yet). What would you suggest to anyone who might be itching to switch over to PythonCall?

Good question! I’m not actually sure as it’s been a while since I used PyCall much - I have implemented all the things I personally find useful. I think PyCall has some nice ways to create new Python classes. PyJulia provides some cool IPython magics.

As for switching, I’d say just try it out. You can use both packages in the same session - and can even share Python objects between them if they are using the same libpython.

---

<div class="post-metadata">

**Author:** ![cjdoris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cjdoris/32/213133_2.png) [@cjdoris](https://discourse.julialang.org/u/cjdoris)\
**Post date:** [February 20, 2022, 2:04pm UTC](https://discourse.julialang.org/t/ann-pythoncall-and-juliacall/76778/9 "2022-02-20T14:04:27Z")

</div>

> [@aplavin](#):
>
> PythonCall already displays matplotlib plots, so it doesn’t need a `PyPlots.jl` equivalent at all? Do you know of any remaining advantages of `PyCall + PyPlots` vs `PythonCall` ?

I’d say that if you are already familiar with Python and Matplotlib then using them via PythonCall works fine. PyPlots and other wrapper libraries still have value by providing a more Julian interface, and maybe integrate into the Julia ecosystem better.

---

<div class="post-metadata">

**Author:** ![cjdoris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cjdoris/32/213133_2.png) [@cjdoris](https://discourse.julialang.org/u/cjdoris)\
**Post date:** [February 20, 2022, 2:08pm UTC](https://discourse.julialang.org/t/ann-pythoncall-and-juliacall/76778/10 "2022-02-20T14:08:49Z")

</div>

> [@aplavin](#):
>
> Currently, I have PyCall’s `~/.julia/conda` taking about 2.5 Gb of space. Would `PythonCall` duplicate all common python dependencies (numpy, scipy, matplotlib, mkl, …) for each Julia env? Or something more clever happens?

I’m not sure about this actually. I suspect a lot of that is the central cache of old downloaded packages. AFAIK Conda never clears that cache automatically - see the `conda clean` command.

That cache doesn’t get copied for each project, but probably the packages you actually use do (I’m not sure if Conda uses symlinks back to the cache?) so if you have many projects using MKL for example they might use a lot of space.

PythonCall is not doing anything clever, just creates a Conda environment like you would at the command line.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [February 20, 2022, 2:19pm UTC](https://discourse.julialang.org/t/ann-pythoncall-and-juliacall/76778/11 "2022-02-20T14:19:50Z")

</div>

Thanks for the detailed responses!  
Indeed, my main concern is the duplication of conda packages. With Pluto, every notebook is its own Julia environment, so duplicating the whole numpy (incl MKL) gets infeasible pretty fast.  
I’m going to try PythonCall and see how much space do environments actually take. The fundamental approach definitely looks cleaner in many aspects compared to PyCall.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [February 20, 2022, 2:21pm UTC](https://discourse.julialang.org/t/ann-pythoncall-and-juliacall/76778/12 "2022-02-20T14:21:38Z")

</div>

> [@cjdoris](#):
>
> I’d say that if you are already familiar with Python and Matplotlib then using them via PythonCall works fine. PyPlots and other wrapper libraries still have value by providing a more Julian interface

I’m currently using PyPlots, and like that they provide basically the same interface and syntax as matplotlib itself. Sounds like PythonCall should be even closer.

---

<div class="post-metadata">

**Author:** ![marius311](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/marius311/32/3953_2.png) [@marius311](https://discourse.julialang.org/u/marius311)\
**Post date:** [February 21, 2022, 12:41am UTC](https://discourse.julialang.org/t/ann-pythoncall-and-juliacall/76778/14 "2022-02-21T00:41:16Z")

</div>

This is really awesome, congratulations! I played around with PythonCall back near your first announcement, and its vastly improved. Couple questions / comments:

- I tried the CondaPkg / JuliaPkg thing which worked flawlessly. I also discovered I can set `JULIA_PYTHONCALL_EXE=$(which python)` and `JULIA_PYTHONCALL_PROJECT="."` to just use an existing Python/Julia enviornment (I’m really liking Poetry on the Python side), which is great for more control. Might be worth documenting this more directly.

- Is there any plan to have anything like PyCall’s string interpolation `py"1+$x"` thing? Personally I find it really handy to be able to just paste some unedited Python code into my Julia scripts and have it work. From what I can tell, this might be the only thing holding me back from switching from PyCall/pyjulia entirely.

- It seems you got around the dreaded `pyjulia` staticially linked libpython issue, which is great. I admit I don’t actually know the details of why that’s an issue for pyjulia, but still curious if you could say what you did differently to get around that?

- Even though its not “needed”, it might still be nice if someone created a “PyPlotCall.jl” package or something, which automatically imported all the standard pyplot stuff and set up Jupyter so you don’t need to return the figure each time.

Anyway, thank you for the great tool!

---

<div class="post-metadata">

**Author:** ![oschulz](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oschulz/32/2998_2.png) [@oschulz](https://discourse.julialang.org/u/oschulz)\
**Post date:** [February 21, 2022, 10:28am UTC](https://discourse.julialang.org/t/ann-pythoncall-and-juliacall/76778/15 "2022-02-21T10:28:19Z")

</div>

Is it possible to override the default location of the Julia environment (`~/.julia/environments/pyjuliapkg` resp. `conda_path/anaconda3/julia_env`) when using `juliacall` from Python? Would be handy if `JULIA_PROJECT` would be used if set, for example.

---

<div class="post-metadata">

**Author:** ![cjdoris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cjdoris/32/213133_2.png) [@cjdoris](https://discourse.julialang.org/u/cjdoris)\
**Post date:** [February 21, 2022, 10:39am UTC](https://discourse.julialang.org/t/ann-pythoncall-and-juliacall/76778/16 "2022-02-21T10:39:15Z")

</div>

> [@marius311](#):
>
> I tried the CondaPkg / JuliaPkg thing which worked flawlessly. I also discovered I can set `JULIA_PYTHONCALL_EXE=$(which python)` and `JULIA_PYTHONCALL_PROJECT="."` to just use an existing Python/Julia enviornment (I’m really liking Poetry on the Python side), which is great for more control. Might be worth documenting this more directly.

Indeed it’s perfectly valid to do that. My next focus is going to be on getting the documentation and testing better. BTW The `JULIA_PYTHONCALL_PROJECT` variable might go, and instead we’ll just respect `JULIA_PROJECT` instead if it is set.

> [@marius311](#):
>
> Is there any plan to have anything like PyCall’s string interpolation `py"1+$x"` thing? Personally I find it really handy to be able to just paste some unedited Python code into my Julia scripts and have it work. From what I can tell, this might be the only thing holding me back from switching from PyCall/pyjulia entirely.

There is [`@pyeval`](https://cjdoris.github.io/PythonCall.jl/stable/pythoncall/#PythonCall.@pyeval) and [`@pyexec`](https://cjdoris.github.io/PythonCall.jl/stable/pythoncall/#PythonCall.@pyexec).

Your example would become `@pyeval x => "1+x"`.

You can also specify the output type like `@pyeval x => "1+x" => Float64`.

> [@marius311](#):
>
> It seems you got around the dreaded `pyjulia` staticially linked libpython issue, which is great. I admit I don’t actually know the details of why that’s an issue for pyjulia, but still curious if you could say what you did differently to get around that?

I’ve encountered that issue before in `pyjulia` but don’t actually know its cause.

I imagine the difference is in how the packages load `libpython`. In JuliaCall, we pass `ctypes.pythonapi._handle` to PythonCall, which is a pointer to an already-open `libpython`. I assume PyJulia/PyCall opens `libpython` itself.

> [@marius311](#):
>
> Even though its not “needed”, it might still be nice if someone created a “PyPlotCall.jl” package or something, which automatically imported all the standard pyplot stuff and set up Jupyter so you don’t need to return the figure each time.

An older version of PythonCall hooked into IJulia so that matplotlib plots were automatically shown, just like they do in IPython. I could certainly add this back.

---

<div class="post-metadata">

**Author:** ![cjdoris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cjdoris/32/213133_2.png) [@cjdoris](https://discourse.julialang.org/u/cjdoris)\
**Post date:** [February 21, 2022, 10:58am UTC](https://discourse.julialang.org/t/ann-pythoncall-and-juliacall/76778/20 "2022-02-21T10:58:23Z")

</div>

> [@oschulz](#):
>
> Is it possible to override the default location of the Julia environment ( `~/.julia/environments/pyjuliapkg` resp. `conda_path/anaconda3/julia_env` ) when using `juliacall` from Python? Would be handy if `JULIA_PROJECT` would be used if set, for example.

Yes indeed, I’ve been thinking that CondaPkg should respect the standard Julia environment variables better. I’ll change it to use `JULIA_PROJECT` if set.

---

<div class="post-metadata">

**Author:** ![oschulz](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oschulz/32/2998_2.png) [@oschulz](https://discourse.julialang.org/u/oschulz)\
**Post date:** [February 21, 2022, 12:39pm UTC](https://discourse.julialang.org/t/ann-pythoncall-and-juliacall/76778/21 "2022-02-21T12:39:24Z")

</div>

> [@cjdoris](#):
>
> I’ll change it to use `JULIA_PROJECT` if set.

Awsome, thanks!

A technical question, if I may: A current issue with `pyjulia` is that there’s trouble [if Phython is statically linked](https://pyjulia.readthedocs.io/en/stable/troubleshooting.html#your-python-interpreter-is-statically-linked-to-libpython). That doesn’t seem to be the case with `juliacall`, how do you get around that issue?

---

<div class="post-metadata">

**Author:** ![cjdoris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cjdoris/32/213133_2.png) [@cjdoris](https://discourse.julialang.org/u/cjdoris)\
**Post date:** [February 21, 2022, 1:38pm UTC](https://discourse.julialang.org/t/ann-pythoncall-and-juliacall/76778/22 "2022-02-21T13:38:36Z")

</div>

> [@oschulz](#):
>
> A technical question, if I may: A current issue with `pyjulia` is that there’s trouble [if Phython is statically linked](https://pyjulia.readthedocs.io/en/stable/troubleshooting.html#your-python-interpreter-is-statically-linked-to-libpython). That doesn’t seem to be the case with `juliacall` , how do you get around that issue?

See my earlier reply to marius311.

---

<div class="post-metadata">

**Author:** ![Seif\_Shebl](https://avatars.discourse-cdn.com/v4/letter/s/eada6e/32.png) [@Seif\_Shebl](https://discourse.julialang.org/u/Seif_Shebl)\
**Post date:** [February 21, 2022, 2:00pm UTC](https://discourse.julialang.org/t/ann-pythoncall-and-juliacall/76778/23 "2022-02-21T14:00:37Z")

</div>

Awesome work. However, I encountered the following error in Julia 1.8.0-DEV when trying the example above and Julia exits immediately:

```julia
julia> using PythonCall

julia> plt = pyimport("matplotlib.pyplot")
Python module: <module 'matplotlib.pyplot' from 'D:\\DeepBook\\.julia\\environments\\v1.8\\.CondaPkg\\env\\lib\\site-packages\\matplotlib\\pyplot.py'>

julia> plt.plot(randn(100))
qt.qpa.plugin: Could not find the Qt platform plugin "windows" in ""
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.

```

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [February 21, 2022, 2:49pm UTC](https://discourse.julialang.org/t/ann-pythoncall-and-juliacall/76778/24 "2022-02-21T14:49:00Z")

</div>

> [@Seif\_Shebl](#):
>
> However, I encountered the following error in Julia 1.8.0-DEV when trying the example above and Julia exits immediately:

Yes, a [huge pain point in PyPlot.jl](https://github.com/JuliaPy/PyPlot.jl/blob/b44e66fd371bf76ac5fd217b155c7157fffd51d6/src/init.jl#L43-L173) was also trying to select/install a working Matplotlib backend by default.

Thanks for your efforts on PythonCall and JuliaCall, BTW. When I created [PyCall eight years ago](https://groups.google.com/g/julia-dev/c/hFL8QxIcxEU/m/xx8LtuyDoAAJ), it was an urgent need to compensate for the relative paucity of Julia packages, but some types of interoperability were still difficult due to Julia limitations. For example, I spent years bugging @jeff.bezanson about [dot overloading](https://github.com/JuliaLang/julia/issues/1974) every time I ran into him, which we finally got 4 years later. Zero-based arrays were not well supported in Julia, and in general it was tricky to decide which Python types should be converted to native Julia types (for convenience) vs wrapped (for efficiency & flexibility) — in hindsight, with the Julia features we have now, I would have left more Python types as-is except for types with lossless round-trip conversions. The very first version of pyjulia was written by Fernando Perez as an “IPython magic”, but it was years before anyone would want to think about calling Julia from Python as more than a cool demo. And, while PyCall could originally link to any `libpython` at runtime, it had to be switched to build-time configuration in order to [improve load times](https://github.com/JuliaPy/PyCall.jl/pull/169); fortunately, Julia’s loading speed has improved since then.

Nowadays, the need to call Python code from Julia is less urgent, though I still am in the habit of using Matplotlib, while the desire to call Julia code from Python in “real” code is growing, and it seems reasonable to re-think many of the design choices that originally went into PyCall. Meanwhile, I haven’t had as much time for PyCall development effort, and while we’ve toyed with the idea of a “PyCall 2.0”, it would be disruptive enough that it’s about as easy to switch to a completely new package. So I’m quite happy to see people actively working on an alternative re-design, and hope that the existing PyCall and PyPlot are helpful in this process.

[Next page](https://discourse.julialang.org/t/ann-pythoncall-and-juliacall/76778.md?page=2)
