Tuesday, July 5, 2016

Jupyter: Write a custom magic that modifies the contents of the cell it's in

Leave a Comment

In a Jupyter notebook there are some built-in magics that change the contents of a notebook cell. For example, the %load magic replaces the contents of the current cell with the contents of a file on the file system.

How can I write a custom magic command that does something similar?

What I have so far prints something to stdout

def tutorial_asset(line):     print('hello world')   def load_ipython_extension(ipython):     ipython.register_magic_function(tutorial_asset, 'line') 

And I can load it with %load_ext tutorial_asset. But from there I'm lost.

[Edit]:

I've found a way to get to the interactive shell instance:

  @magics_class   class MyMagics(Magics):        @line_magic       def tutorial_asset(self, parameters):           self.shell 

The self.shell object seems to give complete access to the set of cells in the notebook, but the only way I can find to modify the cells is to do self.shell.set_next_input('print("hello world")'). This isn't sufficient because, in a Jupyter notebook, that input cell is skipped, and it doesn't overwrite the input cell, it instead creates a new input cell after it.

This would be fine, but if I run the notebook a second time, it creates another input cell with the same file loaded, which is annoying. Can I have it load only once, say, by checking if the contents are already in the next cell?

1 Answers

Answers 1

EDIT: After a little further digging, I found that the current build of notebook cannot do both.

Well, this is a little tricky... Looking at the IPython code, it looks like you need to use set_next_input if you want to replace the cell, and run_cell if you actually want to run some code. However, I can't get both to work at once - it looks like set_next_input always wins.

Digging into the code, the web front-end supports optional clearing of the output on set_next_input. However, the kernel doesn't yet support setting this flag (and so output will always be cleared as the default action). To do better will require a patch to ipykernel.

The best I therefore have is the following code, using jupyter notebook version 4.2.1:

from __future__ import print_function from IPython.core.magic import Magics, magics_class, line_magic  @magics_class class MyMagics(Magics):      @line_magic     def lmagic(self, line):         "Replace current line with new output"         raw_code = 'print("Hello world!")'         # Comment out this line if you actually want to run the code.         self.shell.set_next_input('# %lmagic\n{}'.format(raw_code), replace=True)         # Uncomment this line if you want to run the code instead.         # self.shell.run_cell(raw_code, store_history=False)  ip = get_ipython() ip.register_magics(MyMagics) 

This gives you a magic command lmagic that will either replace the current cell or run the raw_code depending on which bit of the code you have commented out.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment