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