├── .gitignore ├── LICENSE ├── README.md ├── examples.ipynb └── nim_magic.py /.gitignore: -------------------------------------------------------------------------------- 1 | nimmagic/ 2 | nimcache/ 3 | .ipynb_checkpoints/ 4 | .vscode/ 5 | __pycache__/ 6 | nimsuggest.* 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Axel Pahl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nim_magic 2 | 3 | Nim cell magic for JupyterLab or Jupyter Python Notebooks. 4 | 5 | Write Nim modules and use the compiled code directly in the Notebook as extension modules for the Python kernel (similar to e.g. %%cython, but for your favorite language :P ). It builds on @yglukhov 's awesome [nimpy](https://github.com/yglukhov/nimpy) library. 6 | 7 | ## Requirements 8 | * A [Nim](https://nim-lang.org) compiler in your path 9 | * [nimpy](https://github.com/yglukhov/nimpy) package (`nimble install nimpy`) 10 | 11 | ## Installation 12 | Just put the file `nim_magic.py` somewhere in Python's import path, e.g. in one of the dirs that is printed by: `python3 -c "import sys; print(sys.path)"`. 13 | 14 | ## Example 15 | In a JupyterLab or Jupyter Notebook running a Python3 kernel: 16 | 17 | ```Python 18 | # In [1]: 19 | %load_ext nim_magic 20 | 21 | 22 | # In [2]: 23 | %%nim -d:release 24 | proc greet(name: string): string {.exportpy.} = 25 | return "Hello, " & name & "!" 26 | 27 | 28 | # In [3]: 29 | greet("World") 30 | 31 | 32 | # Out [3]: 33 | 'Hello, World!' 34 | 35 | 36 | # In [4] (this will remove temporary dirs created by nim_magic): 37 | %nim_clear 38 | ``` 39 | 40 | Further examples can be found [here](examples.ipynb). 41 | 42 | And there are some gists, too: 43 | * [Accelerating Pearson](https://gist.github.com/apahl/d673b0033794cc5f9514de639285592b): Directly accessing Numpy arrays. 44 | -------------------------------------------------------------------------------- /examples.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%load_ext nim_magic" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "## Simple Example" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": 2, 22 | "metadata": {}, 23 | "outputs": [ 24 | { 25 | "name": "stdout", 26 | "output_type": "stream", 27 | "text": [ 28 | "\n" 29 | ] 30 | } 31 | ], 32 | "source": [ 33 | "%%nim -d:release\n", 34 | "proc greet(name: string): string {.exportpy.} =\n", 35 | " return \"Hello, \" & name & \"!\"" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": 3, 41 | "metadata": {}, 42 | "outputs": [ 43 | { 44 | "data": { 45 | "text/plain": [ 46 | "'Hello, World!'" 47 | ] 48 | }, 49 | "execution_count": 3, 50 | "metadata": {}, 51 | "output_type": "execute_result" 52 | } 53 | ], 54 | "source": [ 55 | "greet(\"World\")" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "Show that re-importing the module after changes to the source code works:" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": 4, 68 | "metadata": {}, 69 | "outputs": [ 70 | { 71 | "name": "stdout", 72 | "output_type": "stream", 73 | "text": [ 74 | "\n" 75 | ] 76 | } 77 | ], 78 | "source": [ 79 | "%%nim -d:release\n", 80 | "proc greet(name: string): string {.exportpy.} =\n", 81 | " return \"Greetings, \" & name & \"!\"" 82 | ] 83 | }, 84 | { 85 | "cell_type": "code", 86 | "execution_count": 5, 87 | "metadata": {}, 88 | "outputs": [ 89 | { 90 | "data": { 91 | "text/plain": [ 92 | "'Greetings, World!'" 93 | ] 94 | }, 95 | "execution_count": 5, 96 | "metadata": {}, 97 | "output_type": "execute_result" 98 | } 99 | ], 100 | "source": [ 101 | "greet(\"World\")" 102 | ] 103 | }, 104 | { 105 | "cell_type": "markdown", 106 | "metadata": {}, 107 | "source": [ 108 | "## Larger Example\n", 109 | "Return a list of primes up to the given number.
\n", 110 | "Show that there actually is a speedup\n", 111 | "\n", 112 | "Python version:" 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": 6, 118 | "metadata": {}, 119 | "outputs": [], 120 | "source": [ 121 | "def py_get_primes(n):\n", 122 | " result = [2, 3]\n", 123 | " curr = 5\n", 124 | " while curr < n:\n", 125 | " is_prime = True\n", 126 | " for i in range(2, curr):\n", 127 | " if curr % i == 0:\n", 128 | " is_prime = False\n", 129 | " break\n", 130 | " if is_prime:\n", 131 | " result.append(curr)\n", 132 | " curr += 2\n", 133 | " return result" 134 | ] 135 | }, 136 | { 137 | "cell_type": "markdown", 138 | "metadata": {}, 139 | "source": [ 140 | "Nim version (using int32):" 141 | ] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": 7, 146 | "metadata": {}, 147 | "outputs": [ 148 | { 149 | "name": "stdout", 150 | "output_type": "stream", 151 | "text": [ 152 | "\n" 153 | ] 154 | } 155 | ], 156 | "source": [ 157 | "%%nim -d:release\n", 158 | "proc nim_get_primes(n: int32): seq[int32] {.exportpy.} =\n", 159 | " result = @[2.int32, 3]\n", 160 | " var curr: int32 = 5\n", 161 | " while curr < n:\n", 162 | " var is_prime = true\n", 163 | " for i in 2.int32 ..< curr:\n", 164 | " if curr mod i == 0:\n", 165 | " is_prime = false\n", 166 | " break\n", 167 | " if is_prime:\n", 168 | " result.add(curr)\n", 169 | " curr += 2" 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": 8, 175 | "metadata": {}, 176 | "outputs": [ 177 | { 178 | "name": "stdout", 179 | "output_type": "stream", 180 | "text": [ 181 | "1229\n" 182 | ] 183 | } 184 | ], 185 | "source": [ 186 | "print(len(py_get_primes(10000)))" 187 | ] 188 | }, 189 | { 190 | "cell_type": "code", 191 | "execution_count": 9, 192 | "metadata": {}, 193 | "outputs": [ 194 | { 195 | "name": "stdout", 196 | "output_type": "stream", 197 | "text": [ 198 | "1229\n" 199 | ] 200 | } 201 | ], 202 | "source": [ 203 | "print(len(nim_get_primes(10000)))" 204 | ] 205 | }, 206 | { 207 | "cell_type": "code", 208 | "execution_count": 10, 209 | "metadata": {}, 210 | "outputs": [ 211 | { 212 | "name": "stdout", 213 | "output_type": "stream", 214 | "text": [ 215 | "383 ms ± 3.17 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" 216 | ] 217 | } 218 | ], 219 | "source": [ 220 | "%timeit py_get_primes(10000)" 221 | ] 222 | }, 223 | { 224 | "cell_type": "code", 225 | "execution_count": 11, 226 | "metadata": {}, 227 | "outputs": [ 228 | { 229 | "name": "stdout", 230 | "output_type": "stream", 231 | "text": [ 232 | "18.1 ms ± 231 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" 233 | ] 234 | } 235 | ], 236 | "source": [ 237 | "%timeit nim_get_primes(10000)" 238 | ] 239 | }, 240 | { 241 | "cell_type": "code", 242 | "execution_count": 12, 243 | "metadata": {}, 244 | "outputs": [ 245 | { 246 | "name": "stdout", 247 | "output_type": "stream", 248 | "text": [ 249 | "Removed temporary files used by nim_magic.\n" 250 | ] 251 | } 252 | ], 253 | "source": [ 254 | "%nim_clear" 255 | ] 256 | }, 257 | { 258 | "cell_type": "code", 259 | "execution_count": null, 260 | "metadata": {}, 261 | "outputs": [], 262 | "source": [] 263 | } 264 | ], 265 | "metadata": { 266 | "kernelspec": { 267 | "display_name": "Python 3", 268 | "language": "python", 269 | "name": "python3" 270 | }, 271 | "language_info": { 272 | "codemirror_mode": { 273 | "name": "ipython", 274 | "version": 3 275 | }, 276 | "file_extension": ".py", 277 | "mimetype": "text/x-python", 278 | "name": "python", 279 | "nbconvert_exporter": "python", 280 | "pygments_lexer": "ipython3", 281 | "version": "3.6.5" 282 | } 283 | }, 284 | "nbformat": 4, 285 | "nbformat_minor": 2 286 | } 287 | -------------------------------------------------------------------------------- /nim_magic.py: -------------------------------------------------------------------------------- 1 | """ 2 | nim_magic.py 3 | Nim cell magic for JupyterLab or Juypter Python Notebooks. 4 | 5 | Write Nim modules and use the compiled code directly in the Notebook as extension modules for the Python kernel (similar to e.g. %%cython, but for your favorite language :-P ). It builds on @yglukhov 's awesome [nimpy](https://github.com/yglukhov/nimpy) library. 6 | 7 | ## Requirements 8 | * A [Nim](https://nim-lang.org) compiler in your path 9 | * [nimpy](https://github.com/yglukhov/nimpy) package (`nimble install nimpy`) 10 | 11 | ## Installation 12 | Just put the file `nim_magic.py` somewhere in Python's import path. 13 | 14 | ## Example 15 | In a JupyterLab or Jupyter Notebook: 16 | 17 | ```Python 18 | # In [1]: 19 | %load_ext nim_magic 20 | 21 | 22 | # In [2]: 23 | %%nim -d:release 24 | proc greet(name: string): string {.exportpy.} = 25 | return "Hello, " & name & "!" 26 | 27 | 28 | # In [3]: 29 | greet("World") 30 | 31 | 32 | # Out [3]: 33 | 'Hello, World!' 34 | 35 | 36 | # In [4] (this will remove temporary dirs created by nim_magic): 37 | %nim_clear 38 | ``` 39 | """ 40 | 41 | import os 42 | import shutil 43 | import sys 44 | import time 45 | import subprocess as sp 46 | from IPython.core.magic import (Magics, magics_class, 47 | line_magic, cell_magic) 48 | 49 | 50 | @magics_class 51 | class NimMagics(Magics): 52 | 53 | @line_magic 54 | def nim_clear(self, cmd): 55 | """%nim_clear 56 | will remove the temporary dirs used by nim_magic.""" 57 | shutil.rmtree('nimmagic', ignore_errors=True) 58 | print("Removed temporary files used by nim_magic.") 59 | 60 | @cell_magic 61 | def nim(self, options, code): 62 | """`options` can be left empty or contain further compile options, e.g. "-d:release" 63 | (separated by space). 64 | 65 | Example: 66 | 67 | %%nim -d:release 68 | """ 69 | if not os.path.isdir("nimmagic"): 70 | os.mkdir("nimmagic") 71 | 72 | glbls = self.shell.user_ns 73 | name = time.strftime("nim%y%m%d%H%M%S") 74 | code = "import nimpy\n\n" + code 75 | ext = "pyd" if sys.platform.startswith('win') else "so" 76 | open("nimmagic/{}.nim".format(name), "w").write(code) 77 | cmd = "nim {1} --hints:off --app:lib --out:nimmagic/{0}.{2} c nimmagic/{0}.nim".format(name, options, ext) 78 | cp = sp.run(cmd, shell=True, check=False, encoding="utf8", stdout=sp.PIPE, stderr=sp.PIPE) 79 | print(cp.stderr) 80 | if cp.returncode == 0: 81 | import_exec = "from nimmagic.{} import *".format(name) 82 | exec(import_exec, glbls) 83 | 84 | 85 | def load_ipython_extension(ipython): 86 | ipython.register_magics(NimMagics) 87 | --------------------------------------------------------------------------------