1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
|
`View the original notebook on nbviewer <http://nbviewer.jupyter.org/github/jupyter/notebook/blob/master/docs/source/examples/Notebook/Importing%20Notebooks.ipynb>`__
Importing Jupyter Notebooks as Modules
======================================
It is a common problem that people want to import code from Jupyter
Notebooks. This is made difficult by the fact that Notebooks are not
plain Python files, and thus cannot be imported by the regular Python
machinery.
Fortunately, Python provides some fairly sophisticated
`hooks <http://www.python.org/dev/peps/pep-0302/>`__ into the import
machinery, so we can actually make Jupyter notebooks importable without
much difficulty, and only using public APIs.
.. code:: python
import io, os, sys, types
.. code:: python
from IPython import get_ipython
from IPython.nbformat import current
from IPython.core.interactiveshell import InteractiveShell
Import hooks typically take the form of two objects:
1. a Module **Loader**, which takes a module name (e.g.
``'IPython.display'``), and returns a Module
2. a Module **Finder**, which figures out whether a module might exist,
and tells Python what **Loader** to use
.. code:: python
def find_notebook(fullname, path=None):
"""find a notebook, given its fully qualified name and an optional path
This turns "foo.bar" into "foo/bar.ipynb"
and tries turning "Foo_Bar" into "Foo Bar" if Foo_Bar
does not exist.
"""
name = fullname.rsplit('.', 1)[-1]
if not path:
path = ['']
for d in path:
nb_path = os.path.join(d, name + ".ipynb")
if os.path.isfile(nb_path):
return nb_path
# let import Notebook_Name find "Notebook Name.ipynb"
nb_path = nb_path.replace("_", " ")
if os.path.isfile(nb_path):
return nb_path
Notebook Loader
---------------
Here we have our Notebook Loader. It's actually quite simple - once we
figure out the filename of the module, all it does is:
1. load the notebook document into memory
2. create an empty Module
3. execute every cell in the Module namespace
Since IPython cells can have extended syntax, the IPython transform is
applied to turn each of these cells into their pure-Python counterparts
before executing them. If all of your notebook cells are pure-Python,
this step is unnecessary.
.. code:: python
class NotebookLoader(object):
"""Module Loader for Jupyter Notebooks"""
def __init__(self, path=None):
self.shell = InteractiveShell.instance()
self.path = path
def load_module(self, fullname):
"""import a notebook as a module"""
path = find_notebook(fullname, self.path)
print ("importing Jupyter notebook from %s" % path)
# load the notebook object
with io.open(path, 'r', encoding='utf-8') as f:
nb = current.read(f, 'json')
# create the module and add it to sys.modules
# if name in sys.modules:
# return sys.modules[name]
mod = types.ModuleType(fullname)
mod.__file__ = path
mod.__loader__ = self
mod.__dict__['get_ipython'] = get_ipython
sys.modules[fullname] = mod
# extra work to ensure that magics that would affect the user_ns
# actually affect the notebook module's ns
save_user_ns = self.shell.user_ns
self.shell.user_ns = mod.__dict__
try:
for cell in nb.worksheets[0].cells:
if cell.cell_type == 'code' and cell.language == 'python':
# transform the input to executable Python
code = self.shell.input_transformer_manager.transform_cell(cell.input)
# run the code in themodule
exec(code, mod.__dict__)
finally:
self.shell.user_ns = save_user_ns
return mod
The Module Finder
-----------------
The finder is a simple object that tells you whether a name can be
imported, and returns the appropriate loader. All this one does is
check, when you do:
.. code:: python
import mynotebook
it checks whether ``mynotebook.ipynb`` exists. If a notebook is found,
then it returns a NotebookLoader.
Any extra logic is just for resolving paths within packages.
.. code:: python
class NotebookFinder(object):
"""Module finder that locates Jupyter Notebooks"""
def __init__(self):
self.loaders = {}
def find_module(self, fullname, path=None):
nb_path = find_notebook(fullname, path)
if not nb_path:
return
key = path
if path:
# lists aren't hashable
key = os.path.sep.join(path)
if key not in self.loaders:
self.loaders[key] = NotebookLoader(path)
return self.loaders[key]
Register the hook
-----------------
Now we register the ``NotebookFinder`` with ``sys.meta_path``
.. code:: python
sys.meta_path.append(NotebookFinder())
After this point, my notebooks should be importable.
Let's look at what we have in the CWD:
.. code:: python
ls nbpackage
So I should be able to ``import nbimp.mynotebook``.
Aside: displaying notebooks
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here is some simple code to display the contents of a notebook with
syntax highlighting, etc.
.. code:: python
from pygments import highlight
from pygments.lexers import PythonLexer
from pygments.formatters import HtmlFormatter
from IPython.display import display, HTML
formatter = HtmlFormatter()
lexer = PythonLexer()
# publish the CSS for pygments highlighting
display(HTML("""
<style type='text/css'>
%s
</style>
""" % formatter.get_style_defs()
))
.. code:: python
def show_notebook(fname):
"""display a short summary of the cells of a notebook"""
with io.open(fname, 'r', encoding='utf-8') as f:
nb = current.read(f, 'json')
html = []
for cell in nb.worksheets[0].cells:
html.append("<h4>%s cell</h4>" % cell.cell_type)
if cell.cell_type == 'code':
html.append(highlight(cell.input, lexer, formatter))
else:
html.append("<pre>%s</pre>" % cell.source)
display(HTML('\n'.join(html)))
show_notebook(os.path.join("nbpackage", "mynotebook.ipynb"))
So my notebook has a heading cell and some code cells, one of which
contains some IPython syntax.
Let's see what happens when we import it
.. code:: python
from nbpackage import mynotebook
Hooray, it imported! Does it work?
.. code:: python
mynotebook.foo()
Hooray again!
Even the function that contains IPython syntax works:
.. code:: python
mynotebook.has_ip_syntax()
Notebooks in packages
---------------------
We also have a notebook inside the ``nb`` package, so let's make sure
that works as well.
.. code:: python
ls nbpackage/nbs
Note that the ``__init__.py`` is necessary for ``nb`` to be considered a
package, just like usual.
.. code:: python
show_notebook(os.path.join("nbpackage", "nbs", "other.ipynb"))
.. code:: python
from nbpackage.nbs import other
other.bar(5)
So now we have importable notebooks, from both the local directory and
inside packages.
I can even put a notebook inside IPython, to further demonstrate that
this is working properly:
.. code:: python
import shutil
from IPython.utils.path import get_ipython_package_dir
utils = os.path.join(get_ipython_package_dir(), 'utils')
shutil.copy(os.path.join("nbpackage", "mynotebook.ipynb"),
os.path.join(utils, "inside_ipython.ipynb")
)
and import the notebook from ``IPython.utils``
.. code:: python
from IPython.utils import inside_ipython
inside_ipython.whatsmyname()
This approach can even import functions and classes that are defined in
a notebook using the ``%%cython`` magic.
`View the original notebook on nbviewer <http://nbviewer.jupyter.org/github/jupyter/notebook/blob/master/docs/source/examples/Notebook/Importing%20Notebooks.ipynb>`__
|