Skip to content

Commit ffdac16

Browse files
author
Christopher Doris
committed
improvements to ipython extension
1 parent 5ec16af commit ffdac16

File tree

4 files changed

+51
-12
lines changed

4 files changed

+51
-12
lines changed

docs/src/compat.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,35 @@ PythonCall.fix_qt_plugin_path
5959

6060
## IPython
6161

62-
The `juliacall.ipython` IPython extension adds these features to your IPython session:
62+
The `juliacall` IPython extension adds these features to your IPython session:
6363
- The line magic `%julia code` executes the given Julia code in-line.
6464
- The cell magic `%%julia` executes a cell of Julia code.
6565
- Julia's `stdout` and `stderr` are redirected to IPython.
6666
- Calling `display(x)` from Julia will display `x` in IPython.
6767

68-
Enable the extension with `%load_ext juliacall.ipython`.
68+
The extension is experimental and unstable - the API can change at any time.
69+
70+
Enable the extension with `%load_ext juliacall`.
6971
See [the IPython docs](https://ipython.readthedocs.io/en/stable/config/extensions/).
7072

73+
The `%%julia` cell magic can synchronise variables between Julia and Python by listing them
74+
on the first line:
75+
```python
76+
In [1]: %load_ext juliacall
77+
78+
In [2]: x = 2
79+
80+
In [3]: y = 8
81+
82+
In [4]: %%julia x y z
83+
...: z = "$x^$y = $(x^y)";
84+
...:
85+
...:
86+
87+
In [5]: z
88+
Out[5]: '2^8 = 256'
89+
```
90+
7191
## Asynchronous Julia code (including Makie)
7292

7393
Asynchronous Julia code will not normally run while Python is executing, unless it is in a

docs/src/releasenotes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
having element type `Any` instead of `Py`.
66
* The `__repr__` method of wrapped Julia objects now uses the 3-arg show method for nicer
77
(richer and truncated) display at the Python REPL.
8+
* The IPython extension can now be loaded as just `%load_ext juliacall`.
9+
* The `%%julia` IPython magic can now synchronise variables between Python and Julia.
810
* Bug fixes.
911

1012
## 0.9.12 (2023-02-28)

pysrc/juliacall/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,7 @@ def jlstr(x):
216216
CONFIG['inited'] = True
217217

218218
init()
219+
220+
def load_ipython_extension(ip):
221+
import juliacall.ipython
222+
juliacall.ipython.load_ipython_extension(ip)

pysrc/juliacall/ipython.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Being experimental, it does not form part of the JuliaCall API. It may be changed or removed
44
in any release.
55
6-
Enable the extension by calling the magic '%load_ext juliacall.ipython'.
6+
Enable the extension by calling the magic '%load_ext juliacall'.
77
88
Features:
99
- Magic `%julia code` to evaluate a piece of Julia code in-line.
@@ -17,15 +17,18 @@
1717
import __main__
1818

1919
_set_var = Main.seval("(k, v) -> @eval $(Symbol(k)) = $v")
20-
_get_var = Main.seval("k -> @eval $(Symbol(k))")
20+
_get_var = Main.seval("k -> hasproperty(Main, Symbol(k)) ? PythonCall.pyjlraw(getproperty(Main, Symbol(k))) : nothing")
21+
_egal = Main.seval("===")
2122

2223
@magics_class
2324
class JuliaMagics(Magics):
2425

2526
@line_cell_magic
2627
def julia(self, line, cell=None):
28+
# parse the line and cell into code and variables
2729
invars = []
2830
outvars = []
31+
syncvars = []
2932
if cell is None:
3033
code = line
3134
else:
@@ -36,15 +39,28 @@ def julia(self, line, cell=None):
3639
elif k.startswith('>'):
3740
outvars.append(k[1:])
3841
else:
39-
invars.append(k)
40-
outvars.append(k)
41-
for k in invars:
42+
syncvars.append(k)
43+
# copy variables to Julia
44+
# keep a cache of variables we may want to copy out again
45+
cachevars = {}
46+
for k in invars + syncvars:
4247
if k in __main__.__dict__:
4348
_set_var(k, __main__.__dict__[k])
49+
if k in syncvars:
50+
cachevars[k] = _get_var(k)
51+
# run the code
4452
ans = Main.seval('begin\n' + code + '\nend')
53+
# flush stderr/stdout
4554
PythonCall._flush_stdio()
46-
for k in outvars:
47-
__main__.__dict__[k] = _get_var(k)
55+
# copy variables back to Python
56+
# only copy those which are new or have changed value
57+
for k in outvars + syncvars:
58+
v0 = cachevars.get(k)
59+
v1 = _get_var(k)
60+
if v1 is not None and (v0 is None or not _egal(v0, v1)):
61+
print(k)
62+
__main__.__dict__[k] = v1._jl_any()
63+
# return the value unless suppressed with trailing ";"
4864
if not code.strip().endswith(';'):
4965
return ans
5066

@@ -84,6 +100,3 @@ def load_ipython_extension(ip):
84100
pushdisplay(IPythonDisplay())
85101
nothing
86102
end""")
87-
88-
def unload_ipython_extension(ip):
89-
pass

0 commit comments

Comments
 (0)