Show More
@@ -1,37 +1,10 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | """ |
|
3 | ===================== | |
|
4 | Cython related magics | |
|
5 | ===================== | |
|
3 | The cython magic has been integrated into Cython itself, | |
|
4 | which is now released in version 0.21. | |
|
6 | 5 | |
|
7 | Magic command interface for interactive work with Cython | |
|
8 | ||
|
9 | .. note:: | |
|
10 | ||
|
11 | The ``Cython`` package needs to be installed separately. It | |
|
12 | can be obtained using ``easy_install`` or ``pip``. | |
|
13 | ||
|
14 | Usage | |
|
15 | ===== | |
|
16 | ||
|
17 | To enable the magics below, execute ``%load_ext cythonmagic``. | |
|
18 | ||
|
19 | ``%%cython`` | |
|
20 | ||
|
21 | {CYTHON_DOC} | |
|
22 | ||
|
23 | ``%%cython_inline`` | |
|
24 | ||
|
25 | {CYTHON_INLINE_DOC} | |
|
26 | ||
|
27 | ``%%cython_pyximport`` | |
|
28 | ||
|
29 | {CYTHON_PYXIMPORT_DOC} | |
|
30 | ||
|
31 | Author: | |
|
32 | * Brian Granger | |
|
33 | ||
|
34 | Parts of this code were taken from Cython.inline. | |
|
6 | cf github `cython` organisation, `cython` repo, under the | |
|
7 | file `Cython/Build/IpythonMagic.py` | |
|
35 | 8 | """ |
|
36 | 9 | #----------------------------------------------------------------------------- |
|
37 | 10 | # Copyright (C) 2010-2011, IPython Development Team. |
@@ -43,303 +16,27 b' Parts of this code were taken from Cython.inline.' | |||
|
43 | 16 | |
|
44 | 17 | from __future__ import print_function |
|
45 | 18 | |
|
46 | import imp | |
|
47 | import io | |
|
48 | import os | |
|
49 | import re | |
|
50 | import sys | |
|
51 | import time | |
|
52 | ||
|
53 | 19 | try: |
|
54 | reload | |
|
55 | except NameError: # Python 3 | |
|
56 | from imp import reload | |
|
20 | import Cython | |
|
21 | except: | |
|
22 | Cython = None | |
|
57 | 23 | |
|
58 | 24 | try: |
|
59 | import hashlib | |
|
60 | except ImportError: | |
|
61 | import md5 as hashlib | |
|
62 | ||
|
63 | from distutils.core import Distribution, Extension | |
|
64 | from distutils.command.build_ext import build_ext | |
|
65 | ||
|
66 | from IPython.core import display | |
|
67 | from IPython.core import magic_arguments | |
|
68 | from IPython.core.magic import Magics, magics_class, cell_magic | |
|
69 | from IPython.utils import py3compat | |
|
70 | from IPython.utils.path import get_ipython_cache_dir | |
|
71 | from IPython.utils.text import dedent | |
|
72 | ||
|
73 | import Cython | |
|
74 | from Cython.Compiler.Errors import CompileError | |
|
75 | from Cython.Build.Dependencies import cythonize | |
|
76 | ||
|
77 | ||
|
78 | @magics_class | |
|
79 | class CythonMagics(Magics): | |
|
80 | ||
|
81 | def __init__(self, shell): | |
|
82 | super(CythonMagics,self).__init__(shell) | |
|
83 | self._reloads = {} | |
|
84 | self._code_cache = {} | |
|
85 | ||
|
86 | def _import_all(self, module): | |
|
87 | for k,v in module.__dict__.items(): | |
|
88 | if not k.startswith('__'): | |
|
89 | self.shell.push({k:v}) | |
|
90 | ||
|
91 | @cell_magic | |
|
92 | def cython_inline(self, line, cell): | |
|
93 | """Compile and run a Cython code cell using Cython.inline. | |
|
94 | ||
|
95 | This magic simply passes the body of the cell to Cython.inline | |
|
96 | and returns the result. If the variables `a` and `b` are defined | |
|
97 | in the user's namespace, here is a simple example that returns | |
|
98 | their sum:: | |
|
99 | ||
|
100 | %%cython_inline | |
|
101 | return a+b | |
|
102 | ||
|
103 | For most purposes, we recommend the usage of the `%%cython` magic. | |
|
104 | """ | |
|
105 | locs = self.shell.user_global_ns | |
|
106 | globs = self.shell.user_ns | |
|
107 | return Cython.inline(cell, locals=locs, globals=globs) | |
|
108 | ||
|
109 | @cell_magic | |
|
110 | def cython_pyximport(self, line, cell): | |
|
111 | """Compile and import a Cython code cell using pyximport. | |
|
25 | from Cython.Build.IpythonMagic import CythonMagics | |
|
26 | except : | |
|
27 | pass | |
|
112 | 28 | |
|
113 | The contents of the cell are written to a `.pyx` file in the current | |
|
114 | working directory, which is then imported using `pyximport`. This | |
|
115 | magic requires a module name to be passed:: | |
|
116 | ||
|
117 | %%cython_pyximport modulename | |
|
118 | def f(x): | |
|
119 | return 2.0*x | |
|
120 | ||
|
121 | The compiled module is then imported and all of its symbols are | |
|
122 | injected into the user's namespace. For most purposes, we recommend | |
|
123 | the usage of the `%%cython` magic. | |
|
124 | """ | |
|
125 | module_name = line.strip() | |
|
126 | if not module_name: | |
|
127 | raise ValueError('module name must be given') | |
|
128 | fname = module_name + '.pyx' | |
|
129 | with io.open(fname, 'w', encoding='utf-8') as f: | |
|
130 | f.write(cell) | |
|
131 | if 'pyximport' not in sys.modules: | |
|
132 | import pyximport | |
|
133 | pyximport.install(reload_support=True) | |
|
134 | if module_name in self._reloads: | |
|
135 | module = self._reloads[module_name] | |
|
136 | reload(module) | |
|
137 | else: | |
|
138 | __import__(module_name) | |
|
139 | module = sys.modules[module_name] | |
|
140 | self._reloads[module_name] = module | |
|
141 | self._import_all(module) | |
|
142 | ||
|
143 | @magic_arguments.magic_arguments() | |
|
144 | @magic_arguments.argument( | |
|
145 | '-c', '--compile-args', action='append', default=[], | |
|
146 | help="Extra flags to pass to compiler via the `extra_compile_args` " | |
|
147 | "Extension flag (can be specified multiple times)." | |
|
148 | ) | |
|
149 | @magic_arguments.argument( | |
|
150 | '--link-args', action='append', default=[], | |
|
151 | help="Extra flags to pass to linker via the `extra_link_args` " | |
|
152 | "Extension flag (can be specified multiple times)." | |
|
153 | ) | |
|
154 | @magic_arguments.argument( | |
|
155 | '-l', '--lib', action='append', default=[], | |
|
156 | help="Add a library to link the extension against (can be specified " | |
|
157 | "multiple times)." | |
|
158 | ) | |
|
159 | @magic_arguments.argument( | |
|
160 | '-n', '--name', | |
|
161 | help="Specify a name for the Cython module." | |
|
162 | ) | |
|
163 | @magic_arguments.argument( | |
|
164 | '-L', dest='library_dirs', metavar='dir', action='append', default=[], | |
|
165 | help="Add a path to the list of libary directories (can be specified " | |
|
166 | "multiple times)." | |
|
167 | ) | |
|
168 | @magic_arguments.argument( | |
|
169 | '-I', '--include', action='append', default=[], | |
|
170 | help="Add a path to the list of include directories (can be specified " | |
|
171 | "multiple times)." | |
|
172 | ) | |
|
173 | @magic_arguments.argument( | |
|
174 | '-+', '--cplus', action='store_true', default=False, | |
|
175 | help="Output a C++ rather than C file." | |
|
176 | ) | |
|
177 | @magic_arguments.argument( | |
|
178 | '-f', '--force', action='store_true', default=False, | |
|
179 | help="Force the compilation of a new module, even if the source has been " | |
|
180 | "previously compiled." | |
|
181 | ) | |
|
182 | @magic_arguments.argument( | |
|
183 | '-a', '--annotate', action='store_true', default=False, | |
|
184 | help="Produce a colorized HTML version of the source." | |
|
185 | ) | |
|
186 | @cell_magic | |
|
187 | def cython(self, line, cell): | |
|
188 | """Compile and import everything from a Cython code cell. | |
|
189 | ||
|
190 | The contents of the cell are written to a `.pyx` file in the | |
|
191 | directory `IPYTHONDIR/cython` using a filename with the hash of the | |
|
192 | code. This file is then cythonized and compiled. The resulting module | |
|
193 | is imported and all of its symbols are injected into the user's | |
|
194 | namespace. The usage is similar to that of `%%cython_pyximport` but | |
|
195 | you don't have to pass a module name:: | |
|
196 | ||
|
197 | %%cython | |
|
198 | def f(x): | |
|
199 | return 2.0*x | |
|
200 | ||
|
201 | To compile OpenMP codes, pass the required `--compile-args` | |
|
202 | and `--link-args`. For example with gcc:: | |
|
203 | ||
|
204 | %%cython --compile-args=-fopenmp --link-args=-fopenmp | |
|
205 | ... | |
|
206 | """ | |
|
207 | args = magic_arguments.parse_argstring(self.cython, line) | |
|
208 | code = cell if cell.endswith('\n') else cell+'\n' | |
|
209 | lib_dir = os.path.join(get_ipython_cache_dir(), 'cython') | |
|
210 | quiet = True | |
|
211 | key = code, sys.version_info, sys.executable, Cython.__version__ | |
|
212 | ||
|
213 | if not os.path.exists(lib_dir): | |
|
214 | os.makedirs(lib_dir) | |
|
215 | ||
|
216 | if args.force: | |
|
217 | # Force a new module name by adding the current time to the | |
|
218 | # key which is hashed to determine the module name. | |
|
219 | key += time.time(), | |
|
220 | ||
|
221 | if args.name: | |
|
222 | module_name = py3compat.unicode_to_str(args.name) | |
|
223 | else: | |
|
224 | module_name = "_cython_magic_" + hashlib.md5(str(key).encode('utf-8')).hexdigest() | |
|
225 | module_path = os.path.join(lib_dir, module_name + self.so_ext) | |
|
226 | ||
|
227 | have_module = os.path.isfile(module_path) | |
|
228 | need_cythonize = not have_module | |
|
229 | ||
|
230 | if args.annotate: | |
|
231 | html_file = os.path.join(lib_dir, module_name + '.html') | |
|
232 | if not os.path.isfile(html_file): | |
|
233 | need_cythonize = True | |
|
234 | ||
|
235 | if need_cythonize: | |
|
236 | c_include_dirs = args.include | |
|
237 | if 'numpy' in code: | |
|
238 | import numpy | |
|
239 | c_include_dirs.append(numpy.get_include()) | |
|
240 | pyx_file = os.path.join(lib_dir, module_name + '.pyx') | |
|
241 | pyx_file = py3compat.cast_bytes_py2(pyx_file, encoding=sys.getfilesystemencoding()) | |
|
242 | with io.open(pyx_file, 'w', encoding='utf-8') as f: | |
|
243 | f.write(code) | |
|
244 | extension = Extension( | |
|
245 | name = module_name, | |
|
246 | sources = [pyx_file], | |
|
247 | include_dirs = c_include_dirs, | |
|
248 | library_dirs = args.library_dirs, | |
|
249 | extra_compile_args = args.compile_args, | |
|
250 | extra_link_args = args.link_args, | |
|
251 | libraries = args.lib, | |
|
252 | language = 'c++' if args.cplus else 'c', | |
|
253 | ) | |
|
254 | build_extension = self._get_build_extension() | |
|
255 | try: | |
|
256 | opts = dict( | |
|
257 | quiet=quiet, | |
|
258 | annotate = args.annotate, | |
|
259 | force = True, | |
|
260 | ) | |
|
261 | build_extension.extensions = cythonize([extension], **opts) | |
|
262 | except CompileError: | |
|
263 | return | |
|
264 | ||
|
265 | if not have_module: | |
|
266 | build_extension.build_temp = os.path.dirname(pyx_file) | |
|
267 | build_extension.build_lib = lib_dir | |
|
268 | build_extension.run() | |
|
269 | self._code_cache[key] = module_name | |
|
270 | ||
|
271 | module = imp.load_dynamic(module_name, module_path) | |
|
272 | self._import_all(module) | |
|
273 | ||
|
274 | if args.annotate: | |
|
275 | try: | |
|
276 | with io.open(html_file, encoding='utf-8') as f: | |
|
277 | annotated_html = f.read() | |
|
278 | except IOError as e: | |
|
279 | # File could not be opened. Most likely the user has a version | |
|
280 | # of Cython before 0.15.1 (when `cythonize` learned the | |
|
281 | # `force` keyword argument) and has already compiled this | |
|
282 | # exact source without annotation. | |
|
283 | print('Cython completed successfully but the annotated ' | |
|
284 | 'source could not be read.', file=sys.stderr) | |
|
285 | print(e, file=sys.stderr) | |
|
286 | else: | |
|
287 | return display.HTML(self.clean_annotated_html(annotated_html)) | |
|
288 | ||
|
289 | @property | |
|
290 | def so_ext(self): | |
|
291 | """The extension suffix for compiled modules.""" | |
|
292 | try: | |
|
293 | return self._so_ext | |
|
294 | except AttributeError: | |
|
295 | self._so_ext = self._get_build_extension().get_ext_filename('') | |
|
296 | return self._so_ext | |
|
297 | ||
|
298 | def _clear_distutils_mkpath_cache(self): | |
|
299 | """clear distutils mkpath cache | |
|
300 | ||
|
301 | prevents distutils from skipping re-creation of dirs that have been removed | |
|
302 | """ | |
|
303 | try: | |
|
304 | from distutils.dir_util import _path_created | |
|
305 | except ImportError: | |
|
306 | pass | |
|
307 | else: | |
|
308 | _path_created.clear() | |
|
309 | ||
|
310 | def _get_build_extension(self): | |
|
311 | self._clear_distutils_mkpath_cache() | |
|
312 | dist = Distribution() | |
|
313 | config_files = dist.find_config_files() | |
|
314 | try: | |
|
315 | config_files.remove('setup.cfg') | |
|
316 | except ValueError: | |
|
317 | pass | |
|
318 | dist.parse_config_files(config_files) | |
|
319 | build_extension = build_ext(dist) | |
|
320 | build_extension.finalize_options() | |
|
321 | return build_extension | |
|
322 | ||
|
323 | @staticmethod | |
|
324 | def clean_annotated_html(html): | |
|
325 | """Clean up the annotated HTML source. | |
|
326 | ||
|
327 | Strips the link to the generated C or C++ file, which we do not | |
|
328 | present to the user. | |
|
329 | """ | |
|
330 | r = re.compile('<p>Raw output: <a href="(.*)">(.*)</a>') | |
|
331 | html = '\n'.join(l for l in html.splitlines() if not r.match(l)) | |
|
332 | return html | |
|
333 | ||
|
334 | __doc__ = __doc__.format( | |
|
335 | # rST doesn't see the -+ flag as part of an option list, so we | |
|
336 | # hide it from the module-level docstring. | |
|
337 | CYTHON_DOC = dedent(CythonMagics.cython.__doc__\ | |
|
338 | .replace('-+, --cplus','--cplus ')), | |
|
339 | CYTHON_INLINE_DOC = dedent(CythonMagics.cython_inline.__doc__), | |
|
340 | CYTHON_PYXIMPORT_DOC = dedent(CythonMagics.cython_pyximport.__doc__), | |
|
341 | ) | |
|
342 | 29 | |
|
30 | ## still load the magic in IPython 3.x, remove completely in future versions. | |
|
343 | 31 | def load_ipython_extension(ip): |
|
344 | 32 | """Load the extension in IPython.""" |
|
345 | ip.register_magics(CythonMagics) | |
|
33 | ||
|
34 | print("""The Cython magic has been move to the Cython package, hence """) | |
|
35 | print("""`%load_ext cythonmagic` is deprecated; Please use `%load_ext cython` instead.""") | |
|
36 | ||
|
37 | if Cython is None or tuple(map(int,Cython.__version__.split('.'))) < (0,21) : | |
|
38 | print("You need Cython version >=0.21 to use the Cython magic") | |
|
39 | return | |
|
40 | if CythonMagics: | |
|
41 | print("""\nThough, because I am nice, I'll still try to load it for you this time.""") | |
|
42 | ip.register_magics(CythonMagics) |
@@ -141,7 +141,6 b" have['pymongo'] = test_for('pymongo')" | |||
|
141 | 141 | have['pygments'] = test_for('pygments') |
|
142 | 142 | have['qt'] = test_for('IPython.external.qt') |
|
143 | 143 | have['sqlite3'] = test_for('sqlite3') |
|
144 | have['cython'] = test_for('Cython') | |
|
145 | 144 | have['tornado'] = test_for('tornado.version_info', (3,1,0), callback=None) |
|
146 | 145 | have['jinja2'] = test_for('jinja2') |
|
147 | 146 | have['mistune'] = test_for('mistune') |
@@ -251,9 +250,6 b" test_sections['kernel.inprocess'].requires('zmq')" | |||
|
251 | 250 | |
|
252 | 251 | # extensions: |
|
253 | 252 | sec = test_sections['extensions'] |
|
254 | if not have['cython']: | |
|
255 | sec.exclude('cythonmagic') | |
|
256 | sec.exclude('tests.test_cythonmagic') | |
|
257 | 253 | # This is deprecated in favour of rpy2 |
|
258 | 254 | sec.exclude('rmagic') |
|
259 | 255 | # autoreload does some strange stuff, so move it to its own test section |
|
1 | NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now