{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Notebooks importieren\n", "\n", "Um modularer entwickeln zu können, ist der Import von Notebooks erforderlich. Da Notebooks jedoch keine Python-Dateien sind, lassen sie sich auch nicht so einfach importieren. Glücklicherweise stellt Python einige Hooks für den Import bereit, sodass IPython-Notebooks schließlich doch importiert werden können." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "execution": { "iopub.execute_input": "2026-05-21T17:35:31.243546Z", "iopub.status.busy": "2026-05-21T17:35:31.243247Z", "iopub.status.idle": "2026-05-21T17:35:31.246409Z", "shell.execute_reply": "2026-05-21T17:35:31.245917Z", "shell.execute_reply.started": "2026-05-21T17:35:31.243525Z" } }, "outputs": [], "source": [ "import sys\n", "import types\n", "\n", "from pathlib import Path" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2026-05-21T17:35:32.191497Z", "iopub.status.busy": "2026-05-21T17:35:32.191183Z", "iopub.status.idle": "2026-05-21T17:35:32.276396Z", "shell.execute_reply": "2026-05-21T17:35:32.276040Z", "shell.execute_reply.started": "2026-05-21T17:35:32.191474Z" } }, "outputs": [], "source": [ "import nbformat\n", "\n", "from IPython import get_ipython\n", "from IPython.core.interactiveshell import InteractiveShell" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Import-Hooks haben normalerweise zwei Objekte:\n", "\n", "* **Module Loader**, der einen Modulnamen (z.B. `IPython.display`) annimmt und ein Modul zurückgibt\n", "* **Module Finder**, der herausfindet, ob ein Modul vorhanden ist, und Python mitteilt, welcher *Loader* verwendet werden soll\n", "\n", "Zunächst jedoch schreiben wir eine Methode, die ein Notebook anhand des vollständig qualifizierten Namen und des optionalen Pfades findet. So wird z.B. aus `mypackage.foo` `mypackage/foo.ipynb` und ersetzt `Foo_Bar` durch `Foo Bar`, wenn `Foo_Bar` nicht existiert." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2026-05-21T17:35:33.500829Z", "iopub.status.busy": "2026-05-21T17:35:33.500290Z", "iopub.status.idle": "2026-05-21T17:35:33.506928Z", "shell.execute_reply": "2026-05-21T17:35:33.506376Z", "shell.execute_reply.started": "2026-05-21T17:35:33.500795Z" } }, "outputs": [], "source": [ "def find_notebook(fullname, path=None):\n", " name = fullname.rsplit(\".\", 1)[-1]\n", " if not path:\n", " path = [\"\"]\n", " for d in path:\n", " nb_path = Path(d, name + \".ipynb\")\n", " if Path(nb_path).isfile():\n", " return nb_path\n", " # Let import Foo find \"Foo.ipynb\"\n", " nb_path = nb_path.replace(\"_\", \" \")\n", " if Path(nb_path).isfile():\n", " return nb_path" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Notebook Loader\n", "\n", "Der Notebook Loader führt die folgenden drei Schritte aus:\n", "\n", "1. Laden des Notebook-Dokuments in den Speicher\n", "2. Erstellen eines leeren Moduls\n", "3. Ausführen jeder Zelle im Modul-Namensraum\n", "\n", " Da IPython-Zellen eine erweiterte Syntax haben können, wird mit `transform_cell` jede Zelle in reinen Python-Code umgewandelt, bevor er ausgeführt wird.\n" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2026-05-21T17:35:34.774609Z", "iopub.status.busy": "2026-05-21T17:35:34.774300Z", "iopub.status.idle": "2026-05-21T17:35:34.779718Z", "shell.execute_reply": "2026-05-21T17:35:34.779171Z", "shell.execute_reply.started": "2026-05-21T17:35:34.774587Z" } }, "outputs": [], "source": [ "class NotebookLoader:\n", " \"\"\"Module Loader for IPython Notebooks\"\"\"\n", "\n", " def __init__(self, path=None):\n", " self.shell = InteractiveShell.instance()\n", " self.path = path\n", "\n", " def load_module(self, fullname):\n", " \"\"\"import a notebook as a module\"\"\"\n", " path = find_notebook(fullname, self.path)\n", "\n", " print(f\"importing notebook from {path}\")\n", "\n", " # load the notebook object\n", " nb = nbformat.read(path, as_version=4)\n", "\n", " # create the module and add it to sys.modules\n", " mod = types.ModuleType(fullname)\n", " mod.__file__ = path\n", " mod.__loader__ = self\n", " mod.__dict__[\"get_ipython\"] = get_ipython\n", " sys.modules[fullname] = mod\n", "\n", " # extra work to ensure that magics that would affect the user_ns\n", " # magics that would affect the user_ns actually affect the\n", " # notebook module's ns\n", " save_user_ns = self.shell.user_ns\n", " self.shell.user_ns = mod.__dict__\n", "\n", " try:\n", " for cell in nb.cells:\n", " if cell.cell_type == \"code\":\n", " # transform the input to executable Python\n", " code = self.shell.input_transformer_manager.transform_cell(\n", " cell.source\n", " )\n", " # run the code in the module\n", " exec(code, mod.__dict__)\n", " finally:\n", " self.shell.user_ns = save_user_ns\n", " return mod" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Notebook Finder\n", "\n", "Der Finder ist ein einfaches Objekt, das angibt, ob ein Notebook anhand seines Dateinamens importiert werden kann, und das den entsprechenden Loader zurückgibt. " ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2026-05-21T17:35:36.250927Z", "iopub.status.busy": "2026-05-21T17:35:36.250561Z", "iopub.status.idle": "2026-05-21T17:35:36.255164Z", "shell.execute_reply": "2026-05-21T17:35:36.254584Z", "shell.execute_reply.started": "2026-05-21T17:35:36.250894Z" } }, "outputs": [], "source": [ "class NotebookFinder:\n", " \"\"\"Module Finder finds the transformed IPython Notebook\"\"\"\n", "\n", " def __init__(self):\n", " self.loaders = {}\n", "\n", " def find_module(self, fullname, path=None):\n", " nb_path = find_notebook(fullname, path)\n", " if not nb_path:\n", " return None\n", "\n", " key = path\n", " if path:\n", " # lists aren't hashable\n", " key = Path(path)\n", "\n", " if key not in self.loaders:\n", " self.loaders[key] = NotebookLoader(path)\n", " return self.loaders[key]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Hook registrieren\n", "\n", "Jetzt registrieren wir `NotebookFinder` mit `sys.meta_path`:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2026-05-21T17:35:37.648732Z", "iopub.status.busy": "2026-05-21T17:35:37.648160Z", "iopub.status.idle": "2026-05-21T17:35:37.652082Z", "shell.execute_reply": "2026-05-21T17:35:37.651558Z", "shell.execute_reply.started": "2026-05-21T17:35:37.648709Z" } }, "outputs": [], "source": [ "sys.meta_path.append(NotebookFinder())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Überprüfen" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Nun sollte unser Notebook [mypackage/foo.ipynb](mypackage/foo.ipynb) importierbar sein mit" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "importing notebook from /Users/veit/cusy/trn/Python4DataScience-de/docs/workspace/ipython/mypackage/foo.ipynb\n" ] } ], "source": [ "from mypackage import foo" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Wird die Python-Methode `bar` ausgeführt?" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'bar'" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "foo.bar()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "… und die IPython-Syntax?" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['debugging.ipynb',\n", " 'display.ipynb',\n", " 'examples.ipynb',\n", " 'extensions.rst',\n", " 'importing.ipynb',\n", " 'index.rst',\n", " 'magics.ipynb',\n", " '\\x1b[34mmypackage\\x1b[m\\x1b[m',\n", " 'myscript.py',\n", " 'shell.ipynb',\n", " 'start.rst',\n", " '\\x1b[31mtab-completion-for-anything.png\\x1b[m\\x1b[m',\n", " '\\x1b[31mtab-completion-for-modules.png\\x1b[m\\x1b[m',\n", " '\\x1b[31mtab-completion-for-objects.png\\x1b[m\\x1b[m',\n", " '\\x1b[34munix-shell\\x1b[m\\x1b[m']" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "foo.has_ip_syntax()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Wiederverwendbarer Import-Hook\n", "\n", "Der Import-Hook kann auch einfach in anderen Notebooks ausgeführt werden mit" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "importing notebook from /Users/veit/cusy/trn/Python4DataScience-de/docs/workspace/ipython/mypackage/foo.ipynb\n" ] } ], "source": [ "%run importing.ipynb" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3.13 Kernel", "language": "python", "name": "python313" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.0" }, "latex_envs": { "LaTeX_envs_menu_present": true, "autoclose": false, "autocomplete": true, "bibliofile": "biblio.bib", "cite_by": "apalike", "current_citInitial": 1, "eqLabelWithNumbers": true, "eqNumInitial": 1, "hotkeys": { "equation": "Ctrl-E", "itemize": "Ctrl-I" }, "labels_anchors": false, "latex_user_defs": false, "report_style_numbering": false, "user_envs_cfg": false }, "widgets": { "application/vnd.jupyter.widget-state+json": { "state": {}, "version_major": 2, "version_minor": 0 } } }, "nbformat": 4, "nbformat_minor": 4 }