From dfd7eba02d03300e7c0dc8249d323e894947e93b Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Wed, 24 Oct 2018 16:43:56 -0700 Subject: [PATCH 01/17] Add more information in README.md [ci skip] --- README.md | 172 ++++++++++++++++++++++++++++++++++++++++++++------ julia/core.py | 2 +- 2 files changed, 154 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 7f77141b..44d5a3d6 100644 --- a/README.md +++ b/README.md @@ -6,57 +6,70 @@ PyJulia Experimenting with developing a better interface to [Julia language](https://julialang.org/) that works with [Python](https://www.python.org/) 2 & 3 and Julia v0.6+. -to run the tests, execute from the toplevel directory - -```shell -tox -``` - -See [Testing](#testing) below for details. - -**Note** You need to explicitly add julia to your `PATH`, an alias will not work. - `pyjulia` is tested against Python versions 2.7, 3.6, and 3.7. Older versions of Python (than 2.7) are not supported. Installation ------------ + +**Note:** If you are using Python installed with Ubuntu or `conda`, +PyJulia does not work by default with standard Python interpreter and +Julia ≥ 0.7. For workarounds, see [Troubleshooting](#troubleshooting) +below. Same caution applies to other Debian-based and possibly other +GNU/Linux distributions. + You will need to install PyCall in your existing Julia installation ```julia +using Pkg # for julia ≥ 0.7 Pkg.add("PyCall") ``` Your python installation must be able to call Julia. If your installer does not add the Julia binary directory to your `PATH`, you will have to -add it. +add it. _An alias will not work._ Then finally you have to install pyjulia. +**Note:** If you are not familiar with `pip` and have some troubles +with the following installation steps, we recommend to go through +[Tutorials in Python Packaging User Guide](https://packaging.python.org/tutorials/). + To get released versions you can use: +```sh +python3 -m pip install --user julia +python2 -m pip install --user julia # If you need Python 2 ``` -pip install julia + +where `--user` should be omitted if you are using virtual environment +(`virtualenv`, `venv`, `conda`, etc.). + +If you are interested in using the development version, you can +install PyJulia directly from GitHub: + +```sh +python3 -m pip install --user 'https://github.com/JuliaPy/pyjulia/archive/master.zip#egg=julia' ``` You may clone it directly to your home directory. ``` git clone https://github.com/JuliaPy/pyjulia - ``` + then inside the pyjulia directory you need to run the python setup file ``` -[sudo] pip install [-e] . +cd pyjulia +python3 -m pip install --user . +python3 -m pip install --user -e . # If you want "development install" ``` -The `-e` flag makes a development install meaning that any change to pyjulia +The `-e` flag makes a development install, meaning that any change to pyjulia source tree will take effect at next python interpreter restart without having to reissue an install command. -`pyjulia` is known to work with `PyCall.jl` ≥ `v0.7.2`. - -If you run into problems using `pyjulia`, first check the version of `PyCall.jl` you have installed by running `Pkg.installed("PyCall")`. +See [Testing](#testing) below for how to run tests. Usage ----- @@ -126,6 +139,84 @@ from julia import Base ``` +Troubleshooting +--------------- + +### Your Python interpreter is statically linked to libpython + +If you use Python installed with Debian-based Linux distribution such +as Ubuntu or install Python by `conda`, you might have noticed that +PyJulia cannot be initialized properly with Julia ≥ 0.7. This is +because those Python executables are statically linked to libpython. +(See [Limitations](#limitations) below for why that's a problem.) + +If you are unsure if your `python` has this problem, you can quickly +check it by: + +```console +$ ldd $(which python) | grep libpython + libpython3.7m.so.1.0 => /usr/lib/libpython3.7m.so.1.0 (0x00007f17c12c4000) +``` + +If it does not print the path to libpython like above, you need to use +one of the workaround below. + +The easiest workaround is to use the `python-jl` command bundled in +PyJulia. This can be used instead of normal `python` command for +basic use-cases such as: + +```console +$ python-jl your_script.py +$ python-jl -c 'from julia.Base import banner; banner()' +$ python-jl -m IPython +``` + +See `python-jl --help` for more information. + +Note that `python-jl` works by launching Python interpreter inside +Julia. If you are comfortable with working in Julia REPL, you can +simply do: + +```julia +julia> using PyCall + +julia> py""" + from julia import Julia + Julia(init_julia=False) + + from your_module_using_pyjulia import function + function() + """ +``` + +Alternatively, you can use [pyenv](https://github.com/pyenv/pyenv) to +build +[`--enable-shared` option](https://github.com/pyenv/pyenv/wiki#how-to-build-cpython-with---enable-shared). +Of course, manually building from Python source distribution with the +same configuration also works. + +```console +$ PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install 3.6.6 +Downloading Python-3.6.6.tar.xz... +-> https://www.python.org/ftp/python/3.6.6/Python-3.6.6.tar.xz +Installing Python-3.6.6... +Installed Python-3.6.6 to /home/USER/.pyenv/versions/3.6.6 + +$ ldd ~/.pyenv/versions/3.6.6/bin/python3.6 | grep libpython + libpython3.6m.so.1.0 => /home/USER/.pyenv/versions/3.6.6/lib/libpython3.6m.so.1.0 (0x00007fca44c8b000) +``` + +For more discussion, see: +https://github.com/JuliaPy/pyjulia/issues/185 + +### Segmentation fault in IPython + +You may experience segmentation fault when using PyJulia in old +versions of IPython. You can avoid this issue by updating IPython to +7.0 or above. Alternatively, you can use IPython via Jupyter (e.g., +`jupyter console`) to workaround the problem. + + How it works ------------ PyJulia loads the `libjulia` library and executes the statements therein. @@ -139,7 +230,50 @@ when reference count drops to zero, so that the Julia object may be freed). Limitations ------------ -Not all valid Julia identifiers are valid Python identifiers. Unicode identifiers are invalid in Python 2.7 and so `pyjulia` cannot call or access Julia methods/variables with names that are not ASCII only. Additionally, it is a common idiom in Julia to append a `!` character to methods which mutate their arguments. These method names are invalid Python identifers. `pyjulia` renames these methods by subsituting `!` with `_b`. For example, the Julia method `sum!` can be called in `pyjulia` using `sum_b(...)`. +### Mismatch in valid set of identifiers + +Not all valid Julia identifiers are valid Python identifiers. Unicode +identifiers are invalid in Python 2.7 and so `pyjulia` cannot call or +access Julia methods/variables with names that are not ASCII only. +Although Python 3 allows Unicode identifiers, they are more +aggressively normalized than Julia. For example, `ϵ` (GREEK LUNATE +EPSILON SYMBOL) and `ε` (GREEK SMALL LETTER EPSILON) are identical in +Python 3 but different in Julia. Additionally, it is a common idiom +in Julia to append a `!` character to methods which mutate their +arguments. These method names are invalid Python identifers. +`pyjulia` renames these methods by subsituting `!` with `_b`. For +example, the Julia method `sum!` can be called in `pyjulia` using +`sum_b(...)`. + +### Pre-compilation mechanism in Julia 1.0 + +There was a major overhaul in the module loading system between Julia +0.6 and 1.0. As a result, +[the hack](https://github.com/JuliaPy/pyjulia/tree/master/julia/fake-julia) +supporting the PyJulia to load PyCall. + +To understand the issue, you need to understand a bit of details in +PyCall implementation. PyCall uses Julia's precompilation mechanism +to reduce JIT compilation required while Julia is loading it. This +results in encoding the path to libpython used by PyCall when it's +loaded from `julia`. Furthermore, libpython ABI such as C struct +layout varies across Python versions. Currently, this is determined +while precompiling PyJulia and cannot be changed at run-time. +Consequently, PyCall only works if it loads the same libpython in +Julia and Python. This is why PyJulia has to be imported in a Python +executable dynamically linked to libpython when using the same PyCall +precompilation cache. + +The aforementioned hack worked by monkey-patching Julia's +precompilation mechanism to emit the precompilation cache file to +other directory when PyCall is used via PyJulia. However, as Juila's +internal for module loading was changed after Juila 0.6, this +monkey-patch does not work anymore. Similar monkey-patch in Julia 1.0 +can be done by using `Base.DEPOT_PATH` although it would waste more +disk space than the similar hack for Julia 0.6. + +For the update on this problem, see: +https://github.com/JuliaLang/julia/issues/28518 Testing diff --git a/julia/core.py b/julia/core.py index 2f946910..4cbf6226 100644 --- a/julia/core.py +++ b/julia/core.py @@ -407,7 +407,7 @@ def is_compatible_exe(jlinfo, _debug=lambda *_: None): See `python-jl --help` for more information. For other available workarounds, see: - https://github.com/JuliaPy/pyjulia/issues/185 + https://github.com/JuliaPy/pyjulia#troubleshooting """ From b4d20a664691e81e116de39004aa879268b87f0a Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Wed, 24 Oct 2018 19:17:21 -0700 Subject: [PATCH 02/17] Clarify the REPL used [ci skip] --- README.md | 64 +++++++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 44d5a3d6..702f43bc 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,8 @@ GNU/Linux distributions. You will need to install PyCall in your existing Julia installation ```julia -using Pkg # for julia ≥ 0.7 -Pkg.add("PyCall") +julia> using Pkg # for julia ≥ 0.7 +julia> Pkg.add("PyCall") ``` Your python installation must be able to call Julia. If your installer @@ -36,9 +36,9 @@ with the following installation steps, we recommend to go through To get released versions you can use: -```sh -python3 -m pip install --user julia -python2 -m pip install --user julia # If you need Python 2 +```console +$ python3 -m pip install --user julia +$ python2 -m pip install --user julia # If you need Python 2 ``` where `--user` should be omitted if you are using virtual environment @@ -47,22 +47,22 @@ where `--user` should be omitted if you are using virtual environment If you are interested in using the development version, you can install PyJulia directly from GitHub: -```sh -python3 -m pip install --user 'https://github.com/JuliaPy/pyjulia/archive/master.zip#egg=julia' +```console +$ python3 -m pip install --user 'https://github.com/JuliaPy/pyjulia/archive/master.zip#egg=julia' ``` You may clone it directly to your home directory. -``` -git clone https://github.com/JuliaPy/pyjulia +```console +$ git clone https://github.com/JuliaPy/pyjulia ``` then inside the pyjulia directory you need to run the python setup file -``` -cd pyjulia -python3 -m pip install --user . -python3 -m pip install --user -e . # If you want "development install" +```console +$ cd pyjulia +$ python3 -m pip install --user . +$ python3 -m pip install --user -e . # If you want "development install" ``` The `-e` flag makes a development install, meaning that any change to pyjulia @@ -83,42 +83,42 @@ which can be used in a customized setup. To call a Julia function in a Julia module, import the Julia module (say `Base`) with: -```python -from julia import Base +```pycon +>>> from julia import Base ``` and then call Julia functions in `Base` from python, e.g., -```python -Base.sind(90) +```pycon +>>> Base.sind(90) ``` Other variants of Python import syntax also work: -```python -import julia.Base -from julia.Base import LinAlg # import a submodule -from julia.Base import sin # import a function from a module +```pycon +>>> import julia.Base +>>> from julia.Base import LinAlg # import a submodule +>>> from julia.Base import sin # import a function from a module ``` The global namespace of Julia's interpreter can be accessed via a special module `julia.Main`: -```python -from julia import Main +```pycon +>>> from julia import Main ``` You can set names in this module to send Python values to Julia: -```python -Main.xs = [1, 2, 3] +```pycon +>>> Main.xs = [1, 2, 3] ``` which allows it to be accessed directly from Julia code, e.g., it can be evaluated at Julia side using Julia syntax: -```python -Main.eval("sin.(xs)") +```pycon +>>> Main.eval("sin.(xs)") ``` ### Low-level interface @@ -127,15 +127,15 @@ If you need a custom setup for `pyjulia`, it must be done *before* importing any Julia modules. For example, to use the Julia executable named `custom_julia`, run: -```python -from julia import Julia -jl = julia.Julia(runtime="custom_julia") +```pycon +>>> from julia import Julia +>>> jl = julia.Julia(runtime="custom_julia") ``` You can then use, e.g., -```python -from julia import Base +```pycon +>>> from julia import Base ``` From 08a59ec16edffe3d7d2e3fe3d54a637ad57277f6 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 25 Oct 2018 18:34:38 -0700 Subject: [PATCH 03/17] Mention `%load_ext julia.magic` in README [ci skip] --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 702f43bc..0fc8a9fc 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,27 @@ You can then use, e.g., >>> from julia import Base ``` +### IPython magic + +In IPython (and therefore in Jupyter), you can directly execute Julia +code using `%%julia` magic: + +``` +In [1]: %load_ext julia.magic +Initializing Julia interpreter. This may take some time... + +In [2]: %%julia + ...: Base.banner(IOContext(stdout, :color=>true)) + _ + _ _ _(_)_ | Documentation: https://docs.julialang.org + (_) | (_) (_) | + _ _ _| |_ __ _ | Type "?" for help, "]?" for Pkg help. + | | | | | | |/ _` | | + | | |_| | | | (_| | | Version 1.0.1 (2018-09-29) + _/ |\__'_|_|_|\__'_| | Official https://julialang.org/ release +|__/ | +``` + Troubleshooting --------------- From 9c2627bc3a99a067e359809f866ee8b6de123528 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 25 Oct 2018 18:53:18 -0700 Subject: [PATCH 04/17] Mention KeyboardInterrupt does not work [ci skip] --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 0fc8a9fc..14b2d567 100644 --- a/README.md +++ b/README.md @@ -296,6 +296,22 @@ disk space than the similar hack for Julia 0.6. For the update on this problem, see: https://github.com/JuliaLang/julia/issues/28518 +### Ctrl-C does not work / terminates the whole Python process + +Currently, initializing PyJulia (e.g., by `from julia import Main`) +disables `KeyboardInterrupt` handling in the Python process. If you +are using normal `python` interpreter, it means that canceling the +input by Ctrl-C does not work and repeatedly providing +Ctrl-C terminates the whole Python process with the error +message `WARNING: Force throwing a SIGINT`. Using IPython 7.0 or +above is recommended to avoid such accidental shutdown. + +It also means that there is no safe way to cancel long-running +computations or I/O at the moment. Sending SIGINT with +Ctrl-C will terminate the whole Python process. + +For the update on this problem, see: +https://github.com/JuliaPy/pyjulia/issues/211 Testing ------- From 4bfdd2ac5b101c75c364debd83fe7ba2771ef779 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 25 Oct 2018 19:03:03 -0700 Subject: [PATCH 05/17] Make ldd usage simpler [ci skip] Since static linking issue can occur in macOS, using `ldd $(which python) | grep libpython` could be confusing. --- README.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 14b2d567..5093ab17 100644 --- a/README.md +++ b/README.md @@ -175,12 +175,22 @@ If you are unsure if your `python` has this problem, you can quickly check it by: ```console -$ ldd $(which python) | grep libpython - libpython3.7m.so.1.0 => /usr/lib/libpython3.7m.so.1.0 (0x00007f17c12c4000) +$ ldd /usr/bin/python + linux-vdso.so.1 (0x00007ffd73f7c000) + libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007f10ef84e000) + libc.so.6 => /usr/lib/libc.so.6 (0x00007f10ef68a000) + libpython3.7m.so.1.0 => /usr/lib/libpython3.7m.so.1.0 (0x00007f10ef116000) + /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f10efaa4000) + libdl.so.2 => /usr/lib/libdl.so.2 (0x00007f10ef111000) + libutil.so.1 => /usr/lib/libutil.so.1 (0x00007f10ef10c000) + libm.so.6 => /usr/lib/libm.so.6 (0x00007f10eef87000) ``` -If it does not print the path to libpython like above, you need to use -one of the workaround below. +in Linux where `/usr/bin/python` should be replaced with the path to +your `python` command (use `which python` to find it out). In macOS, +use `otool -L` instead of `ldd`. If it does not print the path to +libpython like `/usr/lib/libpython3.7m.so.1.0` in above, you need to +use one of the workaround below. The easiest workaround is to use the `python-jl` command bundled in PyJulia. This can be used instead of normal `python` command for From a6e729a0815d2347e6e145bbe5a3f22c251707ca Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 25 Oct 2018 19:20:01 -0700 Subject: [PATCH 06/17] Mention problems with GIL and threads [ci skip] --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index 5093ab17..63364aee 100644 --- a/README.md +++ b/README.md @@ -323,6 +323,34 @@ computations or I/O at the moment. Sending SIGINT with For the update on this problem, see: https://github.com/JuliaPy/pyjulia/issues/211 +### No threading support + +PyJulia cannot be used in different threads since libjulia is not +thread safe. However, you can +[use thread within Julia](https://docs.julialang.org/en/v1.0/manual/parallel-computing/#Multi-Threading-(Experimental)-1). +For example, start IPython by `JULIA_NUM_THREADS=4 ipython` and then +run: + +```julia +In [1]: %load_ext julia.magic +Initializing Julia interpreter. This may take some time... + +In [2]: %%julia + ...: a = zeros(10) + ...: Threads.@threads for i = 1:10 + ...: a[i] = Threads.threadid() + ...: end + ...: a +Out[3]: array([1., 1., 1., 2., 2., 2., 3., 3., 4., 4.]) +``` + +### PyJulia does not release GIL + +PyJulia (or rather PyCall) does not release GIL while calling Julia +functions. It means that Python code and Julia code cannot run in +parallel. + + Testing ------- From 620d2595a269018ee0cab8cc53e12bb87540cb54 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 25 Oct 2018 19:27:10 -0700 Subject: [PATCH 07/17] Mention virtual environments support [ci skip] --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 63364aee..b9c6ef73 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,18 @@ In [2]: %%julia |__/ | ``` +### Virtual environments + +PyJulia can be used in Python virtual environments created by +`virtualenv`, `venv`, and any tools wrapping them such as `pipenv` +provided that Python executable used in such environments are +identical to the one configured with PyCall. If this is not the case, +initializing PyJulia (e.g., `import julia.Main`) prints an informative +error message with detected paths to libjulia. See +[PyCall documentation](https://github.com/JuliaPy/PyCall.jl) for how +to configure Python executable. + +Note that Python environment created by `conda` is not supported. Troubleshooting --------------- From be5cc4a0b2eaf6a251a07f1e13f60c84529b4db8 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 25 Oct 2018 19:41:56 -0700 Subject: [PATCH 08/17] Use "PyJulia" consistently [ci skip] "`pyjulia`" and "pyjulia" are replaced with "PyJulia" (unless it appears as a part of URL). I'm OK with "pyjulia". Using "PyJulia" only because it appears as the title of README.md. --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b9c6ef73..3f573488 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ PyJulia Experimenting with developing a better interface to [Julia language](https://julialang.org/) that works with [Python](https://www.python.org/) 2 & 3 and Julia v0.6+. -`pyjulia` is tested against Python versions 2.7, 3.6, and 3.7. Older versions of Python (than 2.7) are not supported. +PyJulia is tested against Python versions 2.7, 3.6, and 3.7. Older versions of Python (than 2.7) are not supported. Installation ------------ @@ -28,7 +28,7 @@ Your python installation must be able to call Julia. If your installer does not add the Julia binary directory to your `PATH`, you will have to add it. _An alias will not work._ -Then finally you have to install pyjulia. +Then finally you have to install PyJulia. **Note:** If you are not familiar with `pip` and have some troubles with the following installation steps, we recommend to go through @@ -57,7 +57,7 @@ You may clone it directly to your home directory. $ git clone https://github.com/JuliaPy/pyjulia ``` -then inside the pyjulia directory you need to run the python setup file +then inside the `pyjulia` directory you need to run the python setup file ```console $ cd pyjulia @@ -65,7 +65,7 @@ $ python3 -m pip install --user . $ python3 -m pip install --user -e . # If you want "development install" ``` -The `-e` flag makes a development install, meaning that any change to pyjulia +The `-e` flag makes a development install, meaning that any change to PyJulia source tree will take effect at next python interpreter restart without having to reissue an install command. @@ -74,7 +74,7 @@ See [Testing](#testing) below for how to run tests. Usage ----- -`pyjulia` provides a high-level interface which assumes a "normal" +PyJulia provides a high-level interface which assumes a "normal" setup (e.g., `julia` is in your `PATH`) and a low-level interface which can be used in a customized setup. @@ -123,7 +123,7 @@ be evaluated at Julia side using Julia syntax: ### Low-level interface -If you need a custom setup for `pyjulia`, it must be done *before* +If you need a custom setup for PyJulia, it must be done *before* importing any Julia modules. For example, to use the Julia executable named `custom_julia`, run: @@ -276,7 +276,7 @@ Limitations ### Mismatch in valid set of identifiers Not all valid Julia identifiers are valid Python identifiers. Unicode -identifiers are invalid in Python 2.7 and so `pyjulia` cannot call or +identifiers are invalid in Python 2.7 and so PyJulia cannot call or access Julia methods/variables with names that are not ASCII only. Although Python 3 allows Unicode identifiers, they are more aggressively normalized than Julia. For example, `ϵ` (GREEK LUNATE @@ -284,8 +284,8 @@ EPSILON SYMBOL) and `ε` (GREEK SMALL LETTER EPSILON) are identical in Python 3 but different in Julia. Additionally, it is a common idiom in Julia to append a `!` character to methods which mutate their arguments. These method names are invalid Python identifers. -`pyjulia` renames these methods by subsituting `!` with `_b`. For -example, the Julia method `sum!` can be called in `pyjulia` using +PyJulia renames these methods by subsituting `!` with `_b`. For +example, the Julia method `sum!` can be called in PyJulia using `sum_b(...)`. ### Pre-compilation mechanism in Julia 1.0 @@ -392,7 +392,7 @@ PYJULIA_TEST_REBUILD=yes JULIA_EXE=~/julia/julia tox -e py37 -- -s means to execute tests with -* `pyjulia` in shared-cache mode +* PyJulia in shared-cache mode * `julia` executable at `~/julia/julia` * Python 3.7 * `pytest`'s capturing mode turned off From 7a2e87835c2fd7af1ea98463e18c19e69453e9a2 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 25 Oct 2018 20:01:10 -0700 Subject: [PATCH 09/17] Small fixes/tweaks [ci skip] --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 3f573488..e933a809 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,9 @@ julia> using Pkg # for julia ≥ 0.7 julia> Pkg.add("PyCall") ``` -Your python installation must be able to call Julia. If your installer -does not add the Julia binary directory to your `PATH`, you will have to -add it. _An alias will not work._ +Your python installation must be able to call command line program +`julia`. If your installer does not add the Julia binary directory to +your `PATH`, you will have to add it. _An alias will not work._ Then finally you have to install PyJulia. @@ -51,7 +51,7 @@ install PyJulia directly from GitHub: $ python3 -m pip install --user 'https://github.com/JuliaPy/pyjulia/archive/master.zip#egg=julia' ``` -You may clone it directly to your home directory. +You may clone it directly to (say) your home directory. ```console $ git clone https://github.com/JuliaPy/pyjulia @@ -97,7 +97,7 @@ Other variants of Python import syntax also work: ```pycon >>> import julia.Base ->>> from julia.Base import LinAlg # import a submodule +>>> from julia.Base import Enums # import a submodule >>> from julia.Base import sin # import a function from a module ``` @@ -163,8 +163,8 @@ In [2]: %%julia PyJulia can be used in Python virtual environments created by `virtualenv`, `venv`, and any tools wrapping them such as `pipenv` -provided that Python executable used in such environments are -identical to the one configured with PyCall. If this is not the case, +provided that Python executable used in such environments are linked +to identical libpython used by PyCall. If this is not the case, initializing PyJulia (e.g., `import julia.Main`) prints an informative error message with detected paths to libjulia. See [PyCall documentation](https://github.com/JuliaPy/PyCall.jl) for how @@ -293,7 +293,7 @@ example, the Julia method `sum!` can be called in PyJulia using There was a major overhaul in the module loading system between Julia 0.6 and 1.0. As a result, [the hack](https://github.com/JuliaPy/pyjulia/tree/master/julia/fake-julia) -supporting the PyJulia to load PyCall. +supporting the PyJulia to load PyCall stopped working. To understand the issue, you need to understand a bit of details in PyCall implementation. PyCall uses Julia's precompilation mechanism @@ -304,8 +304,8 @@ layout varies across Python versions. Currently, this is determined while precompiling PyJulia and cannot be changed at run-time. Consequently, PyCall only works if it loads the same libpython in Julia and Python. This is why PyJulia has to be imported in a Python -executable dynamically linked to libpython when using the same PyCall -precompilation cache. +executable dynamically linked to libpython, if it shares the same +PyCall precompilation cache. The aforementioned hack worked by monkey-patching Julia's precompilation mechanism to emit the precompilation cache file to From 25217b25422fa9d8b088a4446f0c75e2954eaf85 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Fri, 26 Oct 2018 17:24:17 -0700 Subject: [PATCH 10/17] Tweak a wording [ci skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e933a809..be669cd2 100644 --- a/README.md +++ b/README.md @@ -339,7 +339,7 @@ https://github.com/JuliaPy/pyjulia/issues/211 PyJulia cannot be used in different threads since libjulia is not thread safe. However, you can -[use thread within Julia](https://docs.julialang.org/en/v1.0/manual/parallel-computing/#Multi-Threading-(Experimental)-1). +[use multiple threads within Julia](https://docs.julialang.org/en/v1.0/manual/parallel-computing/#Multi-Threading-(Experimental)-1). For example, start IPython by `JULIA_NUM_THREADS=4 ipython` and then run: From 86ef941751ff6cc86fd9881ce1b6c5a13e27993e Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sun, 28 Oct 2018 14:58:15 -0700 Subject: [PATCH 11/17] Improve some sentences in README.md [ci skip] --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index be669cd2..6e694e33 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,9 @@ Installation ------------ **Note:** If you are using Python installed with Ubuntu or `conda`, -PyJulia does not work by default with standard Python interpreter and -Julia ≥ 0.7. For workarounds, see [Troubleshooting](#troubleshooting) -below. Same caution applies to other Debian-based and possibly other -GNU/Linux distributions. +PyJulia may not work with Julia ≥ 0.7. For workarounds, see +[Troubleshooting](#troubleshooting) below. Same caution applies to +other Debian-based and possibly other GNU/Linux distributions. You will need to install PyCall in your existing Julia installation @@ -298,14 +297,15 @@ supporting the PyJulia to load PyCall stopped working. To understand the issue, you need to understand a bit of details in PyCall implementation. PyCall uses Julia's precompilation mechanism to reduce JIT compilation required while Julia is loading it. This -results in encoding the path to libpython used by PyCall when it's -loaded from `julia`. Furthermore, libpython ABI such as C struct +results in embedding the path to libpython used by PyCall to its +precompilation cache. Furthermore, libpython ABI such as C struct layout varies across Python versions. Currently, this is determined while precompiling PyJulia and cannot be changed at run-time. -Consequently, PyCall only works if it loads the same libpython in -Julia and Python. This is why PyJulia has to be imported in a Python -executable dynamically linked to libpython, if it shares the same -PyCall precompilation cache. +Consequently, PyJulia can use the precompilation cache of PyCall +created by standard Julia module loader only if the PyCall cache is +compiled with the libjulia used by the current Python process. This +is why PyJulia has to be imported in a Python executable dynamically +linked to libpython. The aforementioned hack worked by monkey-patching Julia's precompilation mechanism to emit the precompilation cache file to From 4e2c677011c2730ca30d98d52d9a64cfaba8bfeb Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sun, 28 Oct 2018 21:15:44 -0700 Subject: [PATCH 12/17] Note that simple `tox` works [ci skip] --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 6e694e33..2a6af54c 100644 --- a/README.md +++ b/README.md @@ -366,6 +366,12 @@ parallel. Testing ------- +PyJulia can be tested by simply running [`tox`](http://tox.readthedocs.io): + +```console +$ tox +``` + The full syntax for invoking `tox` is ```shell From 6f2a22effb88d8ca2afccbe0e7f81221d9b83a35 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sun, 28 Oct 2018 21:17:38 -0700 Subject: [PATCH 13/17] Use console syntax in complex tox example [ci skip] --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2a6af54c..aee6653f 100644 --- a/README.md +++ b/README.md @@ -392,8 +392,8 @@ The full syntax for invoking `tox` is For example, -```shell -PYJULIA_TEST_REBUILD=yes JULIA_EXE=~/julia/julia tox -e py37 -- -s +```console +$ PYJULIA_TEST_REBUILD=yes JULIA_EXE=~/julia/julia tox -e py37 -- -s ``` means to execute tests with From e6054f27e78e434c404f11c6691f81cef9e972b7 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Mon, 29 Oct 2018 13:39:19 -0700 Subject: [PATCH 14/17] Tweak wording [ci skip] --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index aee6653f..1138fd76 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Installation **Note:** If you are using Python installed with Ubuntu or `conda`, PyJulia may not work with Julia ≥ 0.7. For workarounds, see [Troubleshooting](#troubleshooting) below. Same caution applies to -other Debian-based and possibly other GNU/Linux distributions. +any Debian-based and possibly other GNU/Linux distributions. You will need to install PyCall in your existing Julia installation @@ -30,7 +30,7 @@ your `PATH`, you will have to add it. _An alias will not work._ Then finally you have to install PyJulia. **Note:** If you are not familiar with `pip` and have some troubles -with the following installation steps, we recommend to go through +with the following installation steps, we recommend going through the [Tutorials in Python Packaging User Guide](https://packaging.python.org/tutorials/). To get released versions you can use: @@ -73,8 +73,8 @@ See [Testing](#testing) below for how to run tests. Usage ----- -PyJulia provides a high-level interface which assumes a "normal" -setup (e.g., `julia` is in your `PATH`) and a low-level interface +PyJulia provides a high-level interface which assumes a "normal" setup +(e.g., `julia` program is in your `PATH`) and a low-level interface which can be used in a customized setup. ### High-level interface @@ -161,7 +161,7 @@ In [2]: %%julia ### Virtual environments PyJulia can be used in Python virtual environments created by -`virtualenv`, `venv`, and any tools wrapping them such as `pipenv` +`virtualenv`, `venv`, and any tools wrapping them such as `pipenv`, provided that Python executable used in such environments are linked to identical libpython used by PyCall. If this is not the case, initializing PyJulia (e.g., `import julia.Main`) prints an informative @@ -200,8 +200,8 @@ $ ldd /usr/bin/python in Linux where `/usr/bin/python` should be replaced with the path to your `python` command (use `which python` to find it out). In macOS, use `otool -L` instead of `ldd`. If it does not print the path to -libpython like `/usr/lib/libpython3.7m.so.1.0` in above, you need to -use one of the workaround below. +libpython like `/usr/lib/libpython3.7m.so.1.0` in above example, you +need to use one of the workaround below. The easiest workaround is to use the `python-jl` command bundled in PyJulia. This can be used instead of normal `python` command for From 04e3360d5e8e01ec5bb7bb1670b3c1ff3a3f910f Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Tue, 30 Oct 2018 18:42:45 -0700 Subject: [PATCH 15/17] Use markdown for fake-julia/README [ci skip] --- julia/fake-julia/{README => README.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename julia/fake-julia/{README => README.md} (100%) diff --git a/julia/fake-julia/README b/julia/fake-julia/README.md similarity index 100% rename from julia/fake-julia/README rename to julia/fake-julia/README.md From fdf49e2631e187f7ef39407d58a43f6e256a3e02 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Tue, 30 Oct 2018 19:23:55 -0700 Subject: [PATCH 16/17] Move the details in fake-julia/README.md [ci skip] --- README.md | 27 +++---------------- julia/fake-julia/README.md | 53 +++++++++++++++++++++++--------------- 2 files changed, 35 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 1138fd76..a89ddcfc 100644 --- a/README.md +++ b/README.md @@ -290,30 +290,9 @@ example, the Julia method `sum!` can be called in PyJulia using ### Pre-compilation mechanism in Julia 1.0 There was a major overhaul in the module loading system between Julia -0.6 and 1.0. As a result, -[the hack](https://github.com/JuliaPy/pyjulia/tree/master/julia/fake-julia) -supporting the PyJulia to load PyCall stopped working. - -To understand the issue, you need to understand a bit of details in -PyCall implementation. PyCall uses Julia's precompilation mechanism -to reduce JIT compilation required while Julia is loading it. This -results in embedding the path to libpython used by PyCall to its -precompilation cache. Furthermore, libpython ABI such as C struct -layout varies across Python versions. Currently, this is determined -while precompiling PyJulia and cannot be changed at run-time. -Consequently, PyJulia can use the precompilation cache of PyCall -created by standard Julia module loader only if the PyCall cache is -compiled with the libjulia used by the current Python process. This -is why PyJulia has to be imported in a Python executable dynamically -linked to libpython. - -The aforementioned hack worked by monkey-patching Julia's -precompilation mechanism to emit the precompilation cache file to -other directory when PyCall is used via PyJulia. However, as Juila's -internal for module loading was changed after Juila 0.6, this -monkey-patch does not work anymore. Similar monkey-patch in Julia 1.0 -can be done by using `Base.DEPOT_PATH` although it would waste more -disk space than the similar hack for Julia 0.6. +0.6 and 1.0. As a result, the "hack" supporting the PyJulia to load +PyCall stopped working. For the implementation detail of the hack, +see: https://github.com/JuliaPy/pyjulia/tree/master/julia/fake-julia For the update on this problem, see: https://github.com/JuliaLang/julia/issues/28518 diff --git a/julia/fake-julia/README.md b/julia/fake-julia/README.md index 604381ee..95cf653a 100644 --- a/julia/fake-julia/README.md +++ b/julia/fake-julia/README.md @@ -1,29 +1,34 @@ This directory contains a python script that pretends to be the julia executable and is used as such to allow julia precompilation to happen in the same environment. -When a Julia module Foo marked with `__precompile__(true)` is imported in Julia, it gets "precompiled" to +When a Julia module `Foo` is imported in Julia, it gets "precompiled" to a Foo.ji cache file that speeds up subsequent loads. See: - https://docs.julialang.org/en/stable/manual/modules/#Module-initialization-and-precompilation-1 -A key thing to understand is that this precompilation works by *launching a new Julia process* -that loads the module in a special "output-ji" mode (by running `julia --output-ji`) that creates -the cache file. +[Module initialization and precompilation](https://docs.julialang.org/en/stable/manual/modules/#Module-initialization-and-precompilation-1) +in Julia manual. PyCall uses this precompilation mechanism to reduce +JIT compilation required during its initialization. This results in +embedding the path to `libpython` used by PyCall to its precompilation +cache. Furthermore, `libpython` ABI such as C struct layout varies +across Python versions. Currently, this is determined while +precompiling PyJulia and cannot be changed at run-time. Consequently, +PyJulia can use the precompilation cache of PyCall created by standard +Julia module loader only if the PyCall cache is compiled with the +`libpython` used by the current Python process. This, of course, +requires the Python executable to be dynamically linked to +`libpython` in the first place. Furthermore, it also applies to any +Julia packages using PyCall. -A second key thing to understand is that pyjulia is using PyCall configured in a different way than -when PyCall is called from with a `julia` process. Within a `julia` process, PyCall works by loading -`libpython` to call the CPython API. Within a `python` process (for `pyjulia`), at least if -`python` is statically linked to `libpython`, PyCall works instead by loading CPython API symbols from -the `python` process itself. This difference affects how PyCall functions are compiled, which means -that *pyjulia cannot use the same PyCall.ji cache file* as julia. This extends to any Julia module -*using* PyCall: every such module needs to have a precompiled cache file that is different from the ordinary -Julia module cache. +If `python` is statically linked to `libpython`, PyJulia has to use +PyCall in a mode that loads CPython API symbols from the `python` +process itself. Generating a precompilation cache compatible with +this mode requires to do it within a _`python`_ process. A key thing +to notice here is that the precompilation in Julia works by *launching +a new process* that loads the module in a special "output-ji" mode (by +running `julia --output-ji`) that creates the cache file. Thus, we +need to configure Julia in such a way that it uses our custom +executable script that behaves like `julia` program for the +precompilation. -The combination of these two facts mean that when PyCall, or any Julia module that uses PyCall, -is loaded from pyjulia with a statically linked `python`, we have to precompile a separate version of it. -Since "normal" precompilation launches a new `julia` process, this process would create the wrong -(`libpython`) version of the PyCall cache file. So, we have to force precompilation to launch -a `python` process, not a `julia` process, so that PyCall is compiled correctly for running inside `python`. - -That is what `fake-julia` does. By changing the `JULIA_HOME` (v0.6) or `JULIA_BINDIR` (v0.7+) environment variable, we trick Julia +That is what `fake-julia` does. By changing the `JULIA_HOME` (v0.6) we trick Julia into launching `fake-julia/julia` instead of the "real" `julia` process during precompilation. `fake-julia/julia` is actually a Python script, but it links `libjulia` and uses `libjulia` to process the command-line arguments, so it mimics the behavior of the `julia` process. Since `fake-julia/julia` is running from within the `python` @@ -34,4 +39,10 @@ compiling PyCall and other Julia modules that use PyCall. For other Julia modu should be identical to the normal Julia cache, so as an optimization `fake-julia/julia` shares the same cache file with the real `julia` in that case.) -See also the discussion in https://github.com/JuliaPy/PyCall.jl/pull/293 and https://github.com/JuliaPy/pyjulia/pull/54 +Unfortunately, this "hack" does not work for Julia 0.7 and above due +to the change in the module loading system. For ongoing discussion, +see: https://github.com/JuliaLang/julia/issues/28518 + +For the discussion during the initial implementation, see also: +https://github.com/JuliaPy/PyCall.jl/pull/293 and +https://github.com/JuliaPy/pyjulia/pull/54 From cd22f4c10443ea5621b3fa9e71d59d14a87b9056 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Tue, 30 Oct 2018 19:35:19 -0700 Subject: [PATCH 17/17] Be precise about about GIL [ci skip] --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a89ddcfc..25c723e8 100644 --- a/README.md +++ b/README.md @@ -337,8 +337,9 @@ Out[3]: array([1., 1., 1., 2., 2., 2., 3., 3., 4., 4.]) ### PyJulia does not release GIL -PyJulia (or rather PyCall) does not release GIL while calling Julia -functions. It means that Python code and Julia code cannot run in +PyJulia does not release the Global Interpreter Lock (GIL) while +calling Julia functions since PyCall expects the GIL to be acquired +always. It means that Python code and Julia code cannot run in parallel.