##// END OF EJS Templates
remove nonfunctional `-la` short arg in cython magic...
MinRK -
Show More
@@ -1,307 +1,307 b''
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 import hashlib
45 import hashlib
46 except ImportError:
46 except ImportError:
47 import md5 as hashlib
47 import md5 as hashlib
48
48
49 from distutils.core import Distribution, Extension
49 from distutils.core import Distribution, Extension
50 from distutils.command.build_ext import build_ext
50 from distutils.command.build_ext import build_ext
51
51
52 from IPython.core import display
52 from IPython.core import display
53 from IPython.core import magic_arguments
53 from IPython.core import magic_arguments
54 from IPython.core.magic import Magics, magics_class, cell_magic
54 from IPython.core.magic import Magics, magics_class, cell_magic
55 from IPython.testing.skipdoctest import skip_doctest
55 from IPython.testing.skipdoctest import skip_doctest
56 from IPython.utils import py3compat
56 from IPython.utils import py3compat
57
57
58 import Cython
58 import Cython
59 from Cython.Compiler.Errors import CompileError
59 from Cython.Compiler.Errors import CompileError
60 from Cython.Build.Dependencies import cythonize
60 from Cython.Build.Dependencies import cythonize
61
61
62
62
63 @magics_class
63 @magics_class
64 class CythonMagics(Magics):
64 class CythonMagics(Magics):
65
65
66 def __init__(self, shell):
66 def __init__(self, shell):
67 super(CythonMagics,self).__init__(shell)
67 super(CythonMagics,self).__init__(shell)
68 self._reloads = {}
68 self._reloads = {}
69 self._code_cache = {}
69 self._code_cache = {}
70
70
71 def _import_all(self, module):
71 def _import_all(self, module):
72 for k,v in module.__dict__.items():
72 for k,v in module.__dict__.items():
73 if not k.startswith('__'):
73 if not k.startswith('__'):
74 self.shell.push({k:v})
74 self.shell.push({k:v})
75
75
76 @cell_magic
76 @cell_magic
77 def cython_inline(self, line, cell):
77 def cython_inline(self, line, cell):
78 """Compile and run a Cython code cell using Cython.inline.
78 """Compile and run a Cython code cell using Cython.inline.
79
79
80 This magic simply passes the body of the cell to Cython.inline
80 This magic simply passes the body of the cell to Cython.inline
81 and returns the result. If the variables `a` and `b` are defined
81 and returns the result. If the variables `a` and `b` are defined
82 in the user's namespace, here is a simple example that returns
82 in the user's namespace, here is a simple example that returns
83 their sum::
83 their sum::
84
84
85 %%cython_inline
85 %%cython_inline
86 return a+b
86 return a+b
87
87
88 For most purposes, we recommend the usage of the `%%cython` magic.
88 For most purposes, we recommend the usage of the `%%cython` magic.
89 """
89 """
90 locs = self.shell.user_global_ns
90 locs = self.shell.user_global_ns
91 globs = self.shell.user_ns
91 globs = self.shell.user_ns
92 return Cython.inline(cell, locals=locs, globals=globs)
92 return Cython.inline(cell, locals=locs, globals=globs)
93
93
94 @cell_magic
94 @cell_magic
95 def cython_pyximport(self, line, cell):
95 def cython_pyximport(self, line, cell):
96 """Compile and import a Cython code cell using pyximport.
96 """Compile and import a Cython code cell using pyximport.
97
97
98 The contents of the cell are written to a `.pyx` file in the current
98 The contents of the cell are written to a `.pyx` file in the current
99 working directory, which is then imported using `pyximport`. This
99 working directory, which is then imported using `pyximport`. This
100 magic requires a module name to be passed::
100 magic requires a module name to be passed::
101
101
102 %%cython_pyximport modulename
102 %%cython_pyximport modulename
103 def f(x):
103 def f(x):
104 return 2.0*x
104 return 2.0*x
105
105
106 The compiled module is then imported and all of its symbols are
106 The compiled module is then imported and all of its symbols are
107 injected into the user's namespace. For most purposes, we recommend
107 injected into the user's namespace. For most purposes, we recommend
108 the usage of the `%%cython` magic.
108 the usage of the `%%cython` magic.
109 """
109 """
110 module_name = line.strip()
110 module_name = line.strip()
111 if not module_name:
111 if not module_name:
112 raise ValueError('module name must be given')
112 raise ValueError('module name must be given')
113 fname = module_name + '.pyx'
113 fname = module_name + '.pyx'
114 with io.open(fname, 'w', encoding='utf-8') as f:
114 with io.open(fname, 'w', encoding='utf-8') as f:
115 f.write(cell)
115 f.write(cell)
116 if 'pyximport' not in sys.modules:
116 if 'pyximport' not in sys.modules:
117 import pyximport
117 import pyximport
118 pyximport.install(reload_support=True)
118 pyximport.install(reload_support=True)
119 if module_name in self._reloads:
119 if module_name in self._reloads:
120 module = self._reloads[module_name]
120 module = self._reloads[module_name]
121 reload(module)
121 reload(module)
122 else:
122 else:
123 __import__(module_name)
123 __import__(module_name)
124 module = sys.modules[module_name]
124 module = sys.modules[module_name]
125 self._reloads[module_name] = module
125 self._reloads[module_name] = module
126 self._import_all(module)
126 self._import_all(module)
127
127
128 @magic_arguments.magic_arguments()
128 @magic_arguments.magic_arguments()
129 @magic_arguments.argument(
129 @magic_arguments.argument(
130 '-c', '--compile-args', action='append', default=[],
130 '-c', '--compile-args', action='append', default=[],
131 help="Extra flags to pass to compiler via the `extra_compile_args` "
131 help="Extra flags to pass to compiler via the `extra_compile_args` "
132 "Extension flag (can be specified multiple times)."
132 "Extension flag (can be specified multiple times)."
133 )
133 )
134 @magic_arguments.argument(
134 @magic_arguments.argument(
135 '-la', '--link-args', action='append', default=[],
135 '--link-args', action='append', default=[],
136 help="Extra flags to pass to linker via the `extra_link_args` "
136 help="Extra flags to pass to linker via the `extra_link_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 '-l', '--lib', action='append', default=[],
140 '-l', '--lib', action='append', default=[],
141 help="Add a library to link the extension against (can be specified "
141 help="Add a library to link the extension against (can be specified "
142 "multiple times)."
142 "multiple times)."
143 )
143 )
144 @magic_arguments.argument(
144 @magic_arguments.argument(
145 '-L', dest='library_dirs', metavar='dir', action='append', default=[],
145 '-L', dest='library_dirs', metavar='dir', action='append', default=[],
146 help="Add a path to the list of libary directories (can be specified "
146 help="Add a path to the list of libary directories (can be specified "
147 "multiple times)."
147 "multiple times)."
148 )
148 )
149 @magic_arguments.argument(
149 @magic_arguments.argument(
150 '-I', '--include', action='append', default=[],
150 '-I', '--include', action='append', default=[],
151 help="Add a path to the list of include directories (can be specified "
151 help="Add a path to the list of include directories (can be specified "
152 "multiple times)."
152 "multiple times)."
153 )
153 )
154 @magic_arguments.argument(
154 @magic_arguments.argument(
155 '-+', '--cplus', action='store_true', default=False,
155 '-+', '--cplus', action='store_true', default=False,
156 help="Output a C++ rather than C file."
156 help="Output a C++ rather than C file."
157 )
157 )
158 @magic_arguments.argument(
158 @magic_arguments.argument(
159 '-f', '--force', action='store_true', default=False,
159 '-f', '--force', action='store_true', default=False,
160 help="Force the compilation of a new module, even if the source has been "
160 help="Force the compilation of a new module, even if the source has been "
161 "previously compiled."
161 "previously compiled."
162 )
162 )
163 @magic_arguments.argument(
163 @magic_arguments.argument(
164 '-a', '--annotate', action='store_true', default=False,
164 '-a', '--annotate', action='store_true', default=False,
165 help="Produce a colorized HTML version of the source."
165 help="Produce a colorized HTML version of the source."
166 )
166 )
167 @cell_magic
167 @cell_magic
168 def cython(self, line, cell):
168 def cython(self, line, cell):
169 """Compile and import everything from a Cython code cell.
169 """Compile and import everything from a Cython code cell.
170
170
171 The contents of the cell are written to a `.pyx` file in the
171 The contents of the cell are written to a `.pyx` file in the
172 directory `IPYTHONDIR/cython` using a filename with the hash of the
172 directory `IPYTHONDIR/cython` using a filename with the hash of the
173 code. This file is then cythonized and compiled. The resulting module
173 code. This file is then cythonized and compiled. The resulting module
174 is imported and all of its symbols are injected into the user's
174 is imported and all of its symbols are injected into the user's
175 namespace. The usage is similar to that of `%%cython_pyximport` but
175 namespace. The usage is similar to that of `%%cython_pyximport` but
176 you don't have to pass a module name::
176 you don't have to pass a module name::
177
177
178 %%cython
178 %%cython
179 def f(x):
179 def f(x):
180 return 2.0*x
180 return 2.0*x
181
181
182 To compile OpenMP codes, pass the required `--compile-args`
182 To compile OpenMP codes, pass the required `--compile-args`
183 and `--link-args`. For example with gcc::
183 and `--link-args`. For example with gcc::
184
184
185 %%cython --compile-args=-fopenmp --link-args=-fopenmp
185 %%cython --compile-args=-fopenmp --link-args=-fopenmp
186 ...
186 ...
187 """
187 """
188 args = magic_arguments.parse_argstring(self.cython, line)
188 args = magic_arguments.parse_argstring(self.cython, line)
189 code = cell if cell.endswith('\n') else cell+'\n'
189 code = cell if cell.endswith('\n') else cell+'\n'
190 lib_dir = os.path.join(self.shell.ipython_dir, 'cython')
190 lib_dir = os.path.join(self.shell.ipython_dir, 'cython')
191 quiet = True
191 quiet = True
192 key = code, sys.version_info, sys.executable, Cython.__version__
192 key = code, sys.version_info, sys.executable, Cython.__version__
193
193
194 if not os.path.exists(lib_dir):
194 if not os.path.exists(lib_dir):
195 os.makedirs(lib_dir)
195 os.makedirs(lib_dir)
196
196
197 if args.force:
197 if args.force:
198 # Force a new module name by adding the current time to the
198 # Force a new module name by adding the current time to the
199 # key which is hashed to determine the module name.
199 # key which is hashed to determine the module name.
200 key += time.time(),
200 key += time.time(),
201
201
202 module_name = "_cython_magic_" + hashlib.md5(str(key).encode('utf-8')).hexdigest()
202 module_name = "_cython_magic_" + hashlib.md5(str(key).encode('utf-8')).hexdigest()
203 module_path = os.path.join(lib_dir, module_name + self.so_ext)
203 module_path = os.path.join(lib_dir, module_name + self.so_ext)
204
204
205 have_module = os.path.isfile(module_path)
205 have_module = os.path.isfile(module_path)
206 need_cythonize = not have_module
206 need_cythonize = not have_module
207
207
208 if args.annotate:
208 if args.annotate:
209 html_file = os.path.join(lib_dir, module_name + '.html')
209 html_file = os.path.join(lib_dir, module_name + '.html')
210 if not os.path.isfile(html_file):
210 if not os.path.isfile(html_file):
211 need_cythonize = True
211 need_cythonize = True
212
212
213 if need_cythonize:
213 if need_cythonize:
214 c_include_dirs = args.include
214 c_include_dirs = args.include
215 if 'numpy' in code:
215 if 'numpy' in code:
216 import numpy
216 import numpy
217 c_include_dirs.append(numpy.get_include())
217 c_include_dirs.append(numpy.get_include())
218 pyx_file = os.path.join(lib_dir, module_name + '.pyx')
218 pyx_file = os.path.join(lib_dir, module_name + '.pyx')
219 pyx_file = py3compat.cast_bytes_py2(pyx_file, encoding=sys.getfilesystemencoding())
219 pyx_file = py3compat.cast_bytes_py2(pyx_file, encoding=sys.getfilesystemencoding())
220 with io.open(pyx_file, 'w', encoding='utf-8') as f:
220 with io.open(pyx_file, 'w', encoding='utf-8') as f:
221 f.write(code)
221 f.write(code)
222 extension = Extension(
222 extension = Extension(
223 name = module_name,
223 name = module_name,
224 sources = [pyx_file],
224 sources = [pyx_file],
225 include_dirs = c_include_dirs,
225 include_dirs = c_include_dirs,
226 library_dirs = args.library_dirs,
226 library_dirs = args.library_dirs,
227 extra_compile_args = args.compile_args,
227 extra_compile_args = args.compile_args,
228 extra_link_args = args.link_args,
228 extra_link_args = args.link_args,
229 libraries = args.lib,
229 libraries = args.lib,
230 language = 'c++' if args.cplus else 'c',
230 language = 'c++' if args.cplus else 'c',
231 )
231 )
232 build_extension = self._get_build_extension()
232 build_extension = self._get_build_extension()
233 try:
233 try:
234 opts = dict(
234 opts = dict(
235 quiet=quiet,
235 quiet=quiet,
236 annotate = args.annotate,
236 annotate = args.annotate,
237 force = True,
237 force = True,
238 )
238 )
239 build_extension.extensions = cythonize([extension], **opts)
239 build_extension.extensions = cythonize([extension], **opts)
240 except CompileError:
240 except CompileError:
241 return
241 return
242
242
243 if not have_module:
243 if not have_module:
244 build_extension.build_temp = os.path.dirname(pyx_file)
244 build_extension.build_temp = os.path.dirname(pyx_file)
245 build_extension.build_lib = lib_dir
245 build_extension.build_lib = lib_dir
246 build_extension.run()
246 build_extension.run()
247 self._code_cache[key] = module_name
247 self._code_cache[key] = module_name
248
248
249 module = imp.load_dynamic(module_name, module_path)
249 module = imp.load_dynamic(module_name, module_path)
250 self._import_all(module)
250 self._import_all(module)
251
251
252 if args.annotate:
252 if args.annotate:
253 try:
253 try:
254 with io.open(html_file, encoding='utf-8') as f:
254 with io.open(html_file, encoding='utf-8') as f:
255 annotated_html = f.read()
255 annotated_html = f.read()
256 except IOError as e:
256 except IOError as e:
257 # File could not be opened. Most likely the user has a version
257 # File could not be opened. Most likely the user has a version
258 # of Cython before 0.15.1 (when `cythonize` learned the
258 # of Cython before 0.15.1 (when `cythonize` learned the
259 # `force` keyword argument) and has already compiled this
259 # `force` keyword argument) and has already compiled this
260 # exact source without annotation.
260 # exact source without annotation.
261 print('Cython completed successfully but the annotated '
261 print('Cython completed successfully but the annotated '
262 'source could not be read.', file=sys.stderr)
262 'source could not be read.', file=sys.stderr)
263 print(e, file=sys.stderr)
263 print(e, file=sys.stderr)
264 else:
264 else:
265 return display.HTML(self.clean_annotated_html(annotated_html))
265 return display.HTML(self.clean_annotated_html(annotated_html))
266
266
267 @property
267 @property
268 def so_ext(self):
268 def so_ext(self):
269 """The extension suffix for compiled modules."""
269 """The extension suffix for compiled modules."""
270 try:
270 try:
271 return self._so_ext
271 return self._so_ext
272 except AttributeError:
272 except AttributeError:
273 self._so_ext = self._get_build_extension().get_ext_filename('')
273 self._so_ext = self._get_build_extension().get_ext_filename('')
274 return self._so_ext
274 return self._so_ext
275
275
276 def _get_build_extension(self):
276 def _get_build_extension(self):
277 dist = Distribution()
277 dist = Distribution()
278 config_files = dist.find_config_files()
278 config_files = dist.find_config_files()
279 try:
279 try:
280 config_files.remove('setup.cfg')
280 config_files.remove('setup.cfg')
281 except ValueError:
281 except ValueError:
282 pass
282 pass
283 dist.parse_config_files(config_files)
283 dist.parse_config_files(config_files)
284 build_extension = build_ext(dist)
284 build_extension = build_ext(dist)
285 build_extension.finalize_options()
285 build_extension.finalize_options()
286 return build_extension
286 return build_extension
287
287
288 @staticmethod
288 @staticmethod
289 def clean_annotated_html(html):
289 def clean_annotated_html(html):
290 """Clean up the annotated HTML source.
290 """Clean up the annotated HTML source.
291
291
292 Strips the link to the generated C or C++ file, which we do not
292 Strips the link to the generated C or C++ file, which we do not
293 present to the user.
293 present to the user.
294 """
294 """
295 r = re.compile('<p>Raw output: <a href="(.*)">(.*)</a>')
295 r = re.compile('<p>Raw output: <a href="(.*)">(.*)</a>')
296 html = '\n'.join(l for l in html.splitlines() if not r.match(l))
296 html = '\n'.join(l for l in html.splitlines() if not r.match(l))
297 return html
297 return html
298
298
299 __doc__ = __doc__.format(
299 __doc__ = __doc__.format(
300 CYTHON_DOC = ' '*8 + CythonMagics.cython.__doc__,
300 CYTHON_DOC = ' '*8 + CythonMagics.cython.__doc__,
301 CYTHON_INLINE_DOC = ' '*8 + CythonMagics.cython_inline.__doc__,
301 CYTHON_INLINE_DOC = ' '*8 + CythonMagics.cython_inline.__doc__,
302 CYTHON_PYXIMPORT_DOC = ' '*8 + CythonMagics.cython_pyximport.__doc__,
302 CYTHON_PYXIMPORT_DOC = ' '*8 + CythonMagics.cython_pyximport.__doc__,
303 )
303 )
304
304
305 def load_ipython_extension(ip):
305 def load_ipython_extension(ip):
306 """Load the extension in IPython."""
306 """Load the extension in IPython."""
307 ip.register_magics(CythonMagics)
307 ip.register_magics(CythonMagics)
General Comments 0
You need to be logged in to leave comments. Login now