|
17 | 17 | import os
|
18 | 18 | import sys
|
19 | 19 |
|
| 20 | +from IPython.core.magic import (Magics, magics_class, line_magic) |
| 21 | +from IPython.core.page import page |
| 22 | +from IPython.utils.ipstruct import Struct |
| 23 | +from IPython.core.error import UsageError |
| 24 | + |
20 | 25 | from _line_profiler import LineProfiler as CLineProfiler
|
21 | 26 |
|
22 | 27 | # Python 2/3 compatibility utils
|
@@ -226,150 +231,138 @@ def show_text(stats, unit, stream=None, stripzeros=False):
|
226 | 231 | for (fn, lineno, name), timings in sorted(stats.items()):
|
227 | 232 | show_func(fn, lineno, name, stats[fn, lineno, name], unit, stream=stream, stripzeros=stripzeros)
|
228 | 233 |
|
229 |
| -# A %lprun magic for IPython. |
230 |
| -def magic_lprun(self, parameter_s=''): |
231 |
| - """ Execute a statement under the line-by-line profiler from the |
232 |
| - line_profiler module. |
| 234 | +@magics_class |
| 235 | +class LineProfilerMagics(Magics): |
233 | 236 |
|
234 |
| - Usage: |
235 |
| - %lprun -f func1 -f func2 <statement> |
| 237 | + @line_magic |
| 238 | + def lprun(self, parameter_s=''): |
| 239 | + """ Execute a statement under the line-by-line profiler from the |
| 240 | + line_profiler module. |
236 | 241 |
|
237 |
| - The given statement (which doesn't require quote marks) is run via the |
238 |
| - LineProfiler. Profiling is enabled for the functions specified by the -f |
239 |
| - options. The statistics will be shown side-by-side with the code through the |
240 |
| - pager once the statement has completed. |
| 242 | + Usage: |
| 243 | + %lprun -f func1 -f func2 <statement> |
241 | 244 |
|
242 |
| - Options: |
| 245 | + The given statement (which doesn't require quote marks) is run via the |
| 246 | + LineProfiler. Profiling is enabled for the functions specified by the -f |
| 247 | + options. The statistics will be shown side-by-side with the code through the |
| 248 | + pager once the statement has completed. |
243 | 249 |
|
244 |
| - -f <function>: LineProfiler only profiles functions and methods it is told |
245 |
| - to profile. This option tells the profiler about these functions. Multiple |
246 |
| - -f options may be used. The argument may be any expression that gives |
247 |
| - a Python function or method object. However, one must be careful to avoid |
248 |
| - spaces that may confuse the option parser. Additionally, functions defined |
249 |
| - in the interpreter at the In[] prompt or via %run currently cannot be |
250 |
| - displayed. Write these functions out to a separate file and import them. |
| 250 | + Options: |
251 | 251 |
|
252 |
| - -m <module>: Get all the functions/methods in a module |
| 252 | + -f <function>: LineProfiler only profiles functions and methods it is told |
| 253 | + to profile. This option tells the profiler about these functions. Multiple |
| 254 | + -f options may be used. The argument may be any expression that gives |
| 255 | + a Python function or method object. However, one must be careful to avoid |
| 256 | + spaces that may confuse the option parser. Additionally, functions defined |
| 257 | + in the interpreter at the In[] prompt or via %run currently cannot be |
| 258 | + displayed. Write these functions out to a separate file and import them. |
253 | 259 |
|
254 |
| - One or more -f or -m options are required to get any useful results. |
| 260 | + -m <module>: Get all the functions/methods in a module |
255 | 261 |
|
256 |
| - -D <filename>: dump the raw statistics out to a pickle file on disk. The |
257 |
| - usual extension for this is ".lprof". These statistics may be viewed later |
258 |
| - by running line_profiler.py as a script. |
| 262 | + One or more -f or -m options are required to get any useful results. |
259 | 263 |
|
260 |
| - -T <filename>: dump the text-formatted statistics with the code side-by-side |
261 |
| - out to a text file. |
| 264 | + -D <filename>: dump the raw statistics out to a pickle file on disk. The |
| 265 | + usual extension for this is ".lprof". These statistics may be viewed later |
| 266 | + by running line_profiler.py as a script. |
262 | 267 |
|
263 |
| - -r: return the LineProfiler object after it has completed profiling. |
| 268 | + -T <filename>: dump the text-formatted statistics with the code side-by-side |
| 269 | + out to a text file. |
264 | 270 |
|
265 |
| - -s: strip out all entries from the print-out that have zeros. |
266 |
| - """ |
267 |
| - # Local imports to avoid hard dependency. |
268 |
| - from distutils.version import LooseVersion |
269 |
| - import IPython |
270 |
| - ipython_version = LooseVersion(IPython.__version__) |
271 |
| - if ipython_version < '0.11': |
272 |
| - from IPython.genutils import page |
273 |
| - from IPython.ipstruct import Struct |
274 |
| - from IPython.ipapi import UsageError |
275 |
| - else: |
276 |
| - from IPython.core.page import page |
277 |
| - from IPython.utils.ipstruct import Struct |
278 |
| - from IPython.core.error import UsageError |
279 |
| - |
280 |
| - # Escape quote markers. |
281 |
| - opts_def = Struct(D=[''], T=[''], f=[], m=[]) |
282 |
| - parameter_s = parameter_s.replace('"', r'\"').replace("'", r"\'") |
283 |
| - opts, arg_str = self.parse_options(parameter_s, 'rsf:m:D:T:', list_all=True) |
284 |
| - opts.merge(opts_def) |
285 |
| - |
286 |
| - global_ns = self.shell.user_global_ns |
287 |
| - local_ns = self.shell.user_ns |
288 |
| - |
289 |
| - # Get the requested functions. |
290 |
| - funcs = [] |
291 |
| - for name in opts.f: |
292 |
| - try: |
293 |
| - funcs.append(eval(name, global_ns, local_ns)) |
294 |
| - except Exception as e: |
295 |
| - raise UsageError('Could not find function %r.\n%s: %s' % (name, |
296 |
| - e.__class__.__name__, e)) |
| 271 | + -r: return the LineProfiler object after it has completed profiling. |
297 | 272 |
|
298 |
| - profile = LineProfiler(*funcs) |
| 273 | + -s: strip out all entries from the print-out that have zeros. |
| 274 | + """ |
299 | 275 |
|
300 |
| - # Get the modules, too |
301 |
| - for modname in opts.m: |
302 |
| - try: |
303 |
| - mod = __import__(modname, fromlist=['']) |
304 |
| - profile.add_module(mod) |
305 |
| - except Exception as e: |
306 |
| - raise UsageError('Could not find module %r.\n%s: %s' % (modname, |
307 |
| - e.__class__.__name__, e)) |
308 |
| - |
309 |
| - # Add the profiler to the builtins for @profile. |
310 |
| - if PY3: |
311 |
| - import builtins |
312 |
| - else: |
313 |
| - import __builtin__ as builtins |
| 276 | + # Escape quote markers. |
| 277 | + opts_def = Struct(D=[''], T=[''], f=[], m=[]) |
| 278 | + parameter_s = parameter_s.replace('"', r'\"').replace("'", r"\'") |
| 279 | + opts, arg_str = self.parse_options(parameter_s, 'rsf:m:D:T:', list_all=True) |
| 280 | + opts.merge(opts_def) |
314 | 281 |
|
315 |
| - if 'profile' in builtins.__dict__: |
316 |
| - had_profile = True |
317 |
| - old_profile = builtins.__dict__['profile'] |
318 |
| - else: |
319 |
| - had_profile = False |
320 |
| - old_profile = None |
321 |
| - builtins.__dict__['profile'] = profile |
| 282 | + global_ns = self.shell.user_global_ns |
| 283 | + local_ns = self.shell.user_ns |
| 284 | + |
| 285 | + # Get the requested functions. |
| 286 | + funcs = [] |
| 287 | + for name in opts.f: |
| 288 | + try: |
| 289 | + funcs.append(eval(name, global_ns, local_ns)) |
| 290 | + except Exception as e: |
| 291 | + raise UsageError('Could not find function %r.\n%s: %s' % (name, |
| 292 | + e.__class__.__name__, e)) |
| 293 | + |
| 294 | + profile = LineProfiler(*funcs) |
| 295 | + |
| 296 | + # Get the modules, too |
| 297 | + for modname in opts.m: |
| 298 | + try: |
| 299 | + mod = __import__(modname, fromlist=['']) |
| 300 | + profile.add_module(mod) |
| 301 | + except Exception as e: |
| 302 | + raise UsageError('Could not find module %r.\n%s: %s' % (modname, |
| 303 | + e.__class__.__name__, e)) |
| 304 | + |
| 305 | + # Add the profiler to the builtins for @profile. |
| 306 | + if PY3: |
| 307 | + import builtins |
| 308 | + else: |
| 309 | + import __builtin__ as builtins |
| 310 | + |
| 311 | + if 'profile' in builtins.__dict__: |
| 312 | + had_profile = True |
| 313 | + old_profile = builtins.__dict__['profile'] |
| 314 | + else: |
| 315 | + had_profile = False |
| 316 | + old_profile = None |
| 317 | + builtins.__dict__['profile'] = profile |
322 | 318 |
|
323 |
| - try: |
324 | 319 | try:
|
325 |
| - profile.runctx(arg_str, global_ns, local_ns) |
326 |
| - message = '' |
327 |
| - except SystemExit: |
328 |
| - message = """*** SystemExit exception caught in code being profiled.""" |
329 |
| - except KeyboardInterrupt: |
330 |
| - message = ("*** KeyboardInterrupt exception caught in code being " |
331 |
| - "profiled.") |
332 |
| - finally: |
333 |
| - if had_profile: |
334 |
| - builtins.__dict__['profile'] = old_profile |
335 |
| - |
336 |
| - # Trap text output. |
337 |
| - stdout_trap = StringIO() |
338 |
| - profile.print_stats(stdout_trap, stripzeros='s' in opts) |
339 |
| - output = stdout_trap.getvalue() |
340 |
| - output = output.rstrip() |
341 |
| - |
342 |
| - if ipython_version < '0.11': |
343 |
| - page(output, screen_lines=self.shell.rc.screen_length) |
344 |
| - else: |
| 320 | + try: |
| 321 | + profile.runctx(arg_str, global_ns, local_ns) |
| 322 | + message = '' |
| 323 | + except SystemExit: |
| 324 | + message = """*** SystemExit exception caught in code being profiled.""" |
| 325 | + except KeyboardInterrupt: |
| 326 | + message = ("*** KeyboardInterrupt exception caught in code being " |
| 327 | + "profiled.") |
| 328 | + finally: |
| 329 | + if had_profile: |
| 330 | + builtins.__dict__['profile'] = old_profile |
| 331 | + |
| 332 | + # Trap text output. |
| 333 | + stdout_trap = StringIO() |
| 334 | + profile.print_stats(stdout_trap, stripzeros='s' in opts) |
| 335 | + output = stdout_trap.getvalue() |
| 336 | + output = output.rstrip() |
| 337 | + |
345 | 338 | page(output)
|
346 |
| - print(message, end="") |
| 339 | + print(message, end="") |
347 | 340 |
|
348 |
| - dump_file = opts.D[0] |
349 |
| - if dump_file: |
350 |
| - profile.dump_stats(dump_file) |
351 |
| - print('\n*** Profile stats pickled to file %r. %s' % ( |
352 |
| - dump_file, message)) |
| 341 | + dump_file = opts.D[0] |
| 342 | + if dump_file: |
| 343 | + profile.dump_stats(dump_file) |
| 344 | + print('\n*** Profile stats pickled to file %r. %s' % ( |
| 345 | + dump_file, message)) |
353 | 346 |
|
354 |
| - text_file = opts.T[0] |
355 |
| - if text_file: |
356 |
| - pfile = open(text_file, 'w') |
357 |
| - pfile.write(output) |
358 |
| - pfile.close() |
359 |
| - print('\n*** Profile printout saved to text file %r. %s' % ( |
360 |
| - text_file, message)) |
| 347 | + text_file = opts.T[0] |
| 348 | + if text_file: |
| 349 | + pfile = open(text_file, 'w') |
| 350 | + pfile.write(output) |
| 351 | + pfile.close() |
| 352 | + print('\n*** Profile printout saved to text file %r. %s' % ( |
| 353 | + text_file, message)) |
361 | 354 |
|
362 |
| - return_value = None |
363 |
| - if 'r' in opts: |
364 |
| - return_value = profile |
| 355 | + return_value = None |
| 356 | + if 'r' in opts: |
| 357 | + return_value = profile |
365 | 358 |
|
366 |
| - return return_value |
| 359 | + return return_value |
367 | 360 |
|
368 | 361 |
|
369 | 362 | def load_ipython_extension(ip):
|
370 | 363 | """ API for IPython to recognize this module as an IPython extension.
|
371 | 364 | """
|
372 |
| - ip.define_magic('lprun', magic_lprun) |
| 365 | + ip.register_magics(LineProfilerMagics) |
373 | 366 |
|
374 | 367 |
|
375 | 368 | def load_stats(filename):
|
|
0 commit comments