{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "from tinykernel import *" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# tinykernel\n", "\n", "> A minimal Python kernel, so you can run Python in your Python." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "All the clever stuff in this library is provided by Python's builtin `ast` module and compilation/exec/eval system, along with [IPython](https://ipython.org/)'s `CachingCompiler` which does some [deep magic](https://cprohm.de/article/better-test-output-with-ast-rewriting-and-a-patched-standard-library.html/). `tinykernel` just brings them together with a little glue." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Install" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With pip:\n", "\n", " pip install tinykernel\n", "\n", "With conda:\n", "\n", " conda install -c fastai tinykernel" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## How to use" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This library provides a single class, `TinyKernel`, which is a tiny persistent kernel for Python code:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "k = TinyKernel()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Call it, passing Python code, to have the code executed in a separate Python environment:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "k(\"a=1\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Expressions return the value of the expression:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "k('a')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "All variables are persisted across calls:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "k(\"a+=1\")\n", "k('a')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Multi-line inputs are supported. If the last line is an expression, it is returned:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "namespace(foo=2)" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "k(\"\"\"import types\n", "b = types.SimpleNamespace(foo=a)\n", "b\"\"\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The original source code is stored, so `inspect.getsource` works and, tracebacks have full details." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'def f(): pass # a comment\\n'" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "k(\"\"\"def f(): pass # a comment\n", "import inspect\n", "inspect.getsource(f)\"\"\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When creating a `TinyKernel`, you can pass a dict of globals to initialize the environment:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'barbar'" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "k = TinyKernel(glb={'foo':'bar'})\n", "k('foo*2')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pass `name` to customize the string that appears in tracebacks (\"kernel\" by default):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "import traceback" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Traceback (most recent call last):\n", " File \"\", line 5, in \n", " try: k(code)\n", " File \"/home/jhoward/git/tinykernel/tinykernel/__init__.py\", line 20, in __call__\n", " if expr: return self._run(Expression(expr.value), nm, 'eval')\n", " File \"/home/jhoward/git/tinykernel/tinykernel/__init__.py\", line 12, in _run\n", " def _run(self, p, nm, mode='exec'): return eval(compiler(p, nm, mode), self.glb)\n", " File \"\", line 3, in \n", " print(f())\n", " File \"\", line 2, in f\n", " return 1/0\n", "ZeroDivisionError: division by zero\n", "\n" ] } ], "source": [ "k = TinyKernel(name='myapp')\n", "code = '''def f():\n", " return 1/0\n", "print(f())'''\n", "try: k(code)\n", "except Exception as e: print(traceback.format_exc())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Acknowledgements" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Thanks to Christopher Prohm, Matthias Bussonnier, and Aaron Meurer for their helpful insights in [this twitter thread](https://twitter.com/jeremyphoward/status/1424990665746763781)." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" } }, "nbformat": 4, "nbformat_minor": 2 }