##// END OF EJS Templates
"smart command mode" for ipdb (like pdb++)....
Antony Lee -
Show More
@@ -1,637 +1,648 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 Pdb debugger class.
4 4
5 5 Modified from the standard pdb.Pdb class to avoid including readline, so that
6 6 the command line completion of other programs which include this isn't
7 7 damaged.
8 8
9 9 In the future, this class will be expanded with improvements over the standard
10 10 pdb.
11 11
12 12 The code in this file is mainly lifted out of cmd.py in Python 2.2, with minor
13 13 changes. Licensing should therefore be under the standard Python terms. For
14 14 details on the PSF (Python Software Foundation) standard license, see:
15 15
16 16 http://www.python.org/2.2.3/license.html"""
17 17
18 18 #*****************************************************************************
19 19 #
20 20 # This file is licensed under the PSF license.
21 21 #
22 22 # Copyright (C) 2001 Python Software Foundation, www.python.org
23 23 # Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu>
24 24 #
25 25 #
26 26 #*****************************************************************************
27 27 from __future__ import print_function
28 28
29 29 import bdb
30 30 import functools
31 31 import inspect
32 32 import sys
33 33
34 34 from IPython import get_ipython
35 35 from IPython.utils import PyColorize, ulinecache
36 36 from IPython.utils import coloransi, py3compat
37 37 from IPython.core.excolors import exception_colors
38 38 from IPython.testing.skipdoctest import skip_doctest
39 39
40 40 # See if we can use pydb.
41 41 has_pydb = False
42 42 prompt = 'ipdb> '
43 43 #We have to check this directly from sys.argv, config struct not yet available
44 44 if '--pydb' in sys.argv:
45 45 try:
46 46 import pydb
47 47 if hasattr(pydb.pydb, "runl") and pydb.version>'1.17':
48 48 # Version 1.17 is broken, and that's what ships with Ubuntu Edgy, so we
49 49 # better protect against it.
50 50 has_pydb = True
51 51 except ImportError:
52 52 print("Pydb (http://bashdb.sourceforge.net/pydb/) does not seem to be available")
53 53
54 54 if has_pydb:
55 55 from pydb import Pdb as OldPdb
56 56 prompt = 'ipydb> '
57 57 else:
58 58 from pdb import Pdb as OldPdb
59 59
60 60 # Allow the set_trace code to operate outside of an ipython instance, even if
61 61 # it does so with some limitations. The rest of this support is implemented in
62 62 # the Tracer constructor.
63 63
64 64 def make_arrow(pad):
65 65 """generate the leading arrow in front of traceback or debugger"""
66 66 if pad >= 2:
67 67 return '-'*(pad-2) + '> '
68 68 elif pad == 1:
69 69 return '>'
70 70 return ''
71 71
72 72
73 73 def BdbQuit_excepthook(et, ev, tb, excepthook=None):
74 74 """Exception hook which handles `BdbQuit` exceptions.
75 75
76 76 All other exceptions are processed using the `excepthook`
77 77 parameter.
78 78 """
79 79 if et==bdb.BdbQuit:
80 80 print('Exiting Debugger.')
81 81 elif excepthook is not None:
82 82 excepthook(et, ev, tb)
83 83 else:
84 84 # Backwards compatibility. Raise deprecation warning?
85 85 BdbQuit_excepthook.excepthook_ori(et,ev,tb)
86 86
87 87 def BdbQuit_IPython_excepthook(self,et,ev,tb,tb_offset=None):
88 88 print('Exiting Debugger.')
89 89
90 90
91 91 class Tracer(object):
92 92 """Class for local debugging, similar to pdb.set_trace.
93 93
94 94 Instances of this class, when called, behave like pdb.set_trace, but
95 95 providing IPython's enhanced capabilities.
96 96
97 97 This is implemented as a class which must be initialized in your own code
98 98 and not as a standalone function because we need to detect at runtime
99 99 whether IPython is already active or not. That detection is done in the
100 100 constructor, ensuring that this code plays nicely with a running IPython,
101 101 while functioning acceptably (though with limitations) if outside of it.
102 102 """
103 103
104 104 @skip_doctest
105 105 def __init__(self, colors=None):
106 106 """Create a local debugger instance.
107 107
108 108 Parameters
109 109 ----------
110 110
111 111 colors : str, optional
112 112 The name of the color scheme to use, it must be one of IPython's
113 113 valid color schemes. If not given, the function will default to
114 114 the current IPython scheme when running inside IPython, and to
115 115 'NoColor' otherwise.
116 116
117 117 Examples
118 118 --------
119 119 ::
120 120
121 121 from IPython.core.debugger import Tracer; debug_here = Tracer()
122 122
123 123 Later in your code::
124 124
125 125 debug_here() # -> will open up the debugger at that point.
126 126
127 127 Once the debugger activates, you can use all of its regular commands to
128 128 step through code, set breakpoints, etc. See the pdb documentation
129 129 from the Python standard library for usage details.
130 130 """
131 131
132 132 ip = get_ipython()
133 133 if ip is None:
134 134 # Outside of ipython, we set our own exception hook manually
135 135 sys.excepthook = functools.partial(BdbQuit_excepthook,
136 136 excepthook=sys.excepthook)
137 137 def_colors = 'NoColor'
138 138 try:
139 139 # Limited tab completion support
140 140 import readline
141 141 readline.parse_and_bind('tab: complete')
142 142 except ImportError:
143 143 pass
144 144 else:
145 145 # In ipython, we use its custom exception handler mechanism
146 146 def_colors = ip.colors
147 147 ip.set_custom_exc((bdb.BdbQuit,), BdbQuit_IPython_excepthook)
148 148
149 149 if colors is None:
150 150 colors = def_colors
151 151
152 152 # The stdlib debugger internally uses a modified repr from the `repr`
153 153 # module, that limits the length of printed strings to a hardcoded
154 154 # limit of 30 characters. That much trimming is too aggressive, let's
155 155 # at least raise that limit to 80 chars, which should be enough for
156 156 # most interactive uses.
157 157 try:
158 158 try:
159 159 from reprlib import aRepr # Py 3
160 160 except ImportError:
161 161 from repr import aRepr # Py 2
162 162 aRepr.maxstring = 80
163 163 except:
164 164 # This is only a user-facing convenience, so any error we encounter
165 165 # here can be warned about but can be otherwise ignored. These
166 166 # printouts will tell us about problems if this API changes
167 167 import traceback
168 168 traceback.print_exc()
169 169
170 170 self.debugger = Pdb(colors)
171 171
172 172 def __call__(self):
173 173 """Starts an interactive debugger at the point where called.
174 174
175 175 This is similar to the pdb.set_trace() function from the std lib, but
176 176 using IPython's enhanced debugger."""
177 177
178 178 self.debugger.set_trace(sys._getframe().f_back)
179 179
180 180
181 181 def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
182 182 """Make new_fn have old_fn's doc string. This is particularly useful
183 183 for the ``do_...`` commands that hook into the help system.
184 184 Adapted from from a comp.lang.python posting
185 185 by Duncan Booth."""
186 186 def wrapper(*args, **kw):
187 187 return new_fn(*args, **kw)
188 188 if old_fn.__doc__:
189 189 wrapper.__doc__ = old_fn.__doc__ + additional_text
190 190 return wrapper
191 191
192 192
193 193 def _file_lines(fname):
194 194 """Return the contents of a named file as a list of lines.
195 195
196 196 This function never raises an IOError exception: if the file can't be
197 197 read, it simply returns an empty list."""
198 198
199 199 try:
200 200 outfile = open(fname)
201 201 except IOError:
202 202 return []
203 203 else:
204 204 out = outfile.readlines()
205 205 outfile.close()
206 206 return out
207 207
208 208
209 209 class Pdb(OldPdb):
210 210 """Modified Pdb class, does not load readline."""
211 211
212 212 def __init__(self,color_scheme='NoColor',completekey=None,
213 213 stdin=None, stdout=None, context=5):
214 214
215 215 # Parent constructor:
216 216 try:
217 217 self.context=int(context)
218 218 if self.context <= 0:
219 219 raise ValueError("Context must be a positive integer")
220 220 except (TypeError, ValueError):
221 221 raise ValueError("Context must be a positive integer")
222 222
223 223 if has_pydb and completekey is None:
224 224 OldPdb.__init__(self,stdin=stdin,stdout=sys.stdout)
225 225 else:
226 226 OldPdb.__init__(self,completekey,stdin,stdout)
227 227
228 228 # IPython changes...
229 229 self.is_pydb = has_pydb
230 230
231 231 self.shell = get_ipython()
232 232
233 233 if self.shell is None:
234 234 # No IPython instance running, we must create one
235 235 from IPython.terminal.interactiveshell import \
236 236 TerminalInteractiveShell
237 237 self.shell = TerminalInteractiveShell.instance()
238 238
239 239 if self.is_pydb:
240 240
241 241 # interactiveshell.py's ipalias seems to want pdb's checkline
242 242 # which located in pydb.fn
243 243 import pydb.fns
244 244 self.checkline = lambda filename, lineno: \
245 245 pydb.fns.checkline(self, filename, lineno)
246 246
247 247 self.curframe = None
248 248 self.do_restart = self.new_do_restart
249 249
250 250 self.old_all_completions = self.shell.Completer.all_completions
251 251 self.shell.Completer.all_completions=self.all_completions
252 252
253 253 self.do_list = decorate_fn_with_doc(self.list_command_pydb,
254 254 OldPdb.do_list)
255 255 self.do_l = self.do_list
256 256 self.do_frame = decorate_fn_with_doc(self.new_do_frame,
257 257 OldPdb.do_frame)
258 258
259 259 self.aliases = {}
260 260
261 261 # Create color table: we copy the default one from the traceback
262 262 # module and add a few attributes needed for debugging
263 263 self.color_scheme_table = exception_colors()
264 264
265 265 # shorthands
266 266 C = coloransi.TermColors
267 267 cst = self.color_scheme_table
268 268
269 269 cst['NoColor'].colors.prompt = C.NoColor
270 270 cst['NoColor'].colors.breakpoint_enabled = C.NoColor
271 271 cst['NoColor'].colors.breakpoint_disabled = C.NoColor
272 272
273 273 cst['Linux'].colors.prompt = C.Green
274 274 cst['Linux'].colors.breakpoint_enabled = C.LightRed
275 275 cst['Linux'].colors.breakpoint_disabled = C.Red
276 276
277 277 cst['LightBG'].colors.prompt = C.Blue
278 278 cst['LightBG'].colors.breakpoint_enabled = C.LightRed
279 279 cst['LightBG'].colors.breakpoint_disabled = C.Red
280 280
281 281 self.set_colors(color_scheme)
282 282
283 283 # Add a python parser so we can syntax highlight source while
284 284 # debugging.
285 285 self.parser = PyColorize.Parser()
286 286
287 287 # Set the prompt - the default prompt is '(Pdb)'
288 288 Colors = cst.active_colors
289 289 if color_scheme == 'NoColor':
290 290 self.prompt = prompt
291 291 else:
292 292 # The colour markers are wrapped by bytes 01 and 02 so that readline
293 293 # can calculate the width.
294 294 self.prompt = u'\x01%s\x02%s\x01%s\x02' % (Colors.prompt, prompt, Colors.Normal)
295 295
296 296 def set_colors(self, scheme):
297 297 """Shorthand access to the color table scheme selector method."""
298 298 self.color_scheme_table.set_active_scheme(scheme)
299 299
300 300 def interaction(self, frame, traceback):
301 301 self.shell.set_completer_frame(frame)
302 302 while True:
303 303 try:
304 304 OldPdb.interaction(self, frame, traceback)
305 305 break
306 306 except KeyboardInterrupt:
307 307 self.shell.write('\n' + self.shell.get_exception_only())
308 308 break
309 309 finally:
310 310 # Pdb sets readline delimiters, so set them back to our own
311 311 if self.shell.readline is not None:
312 312 self.shell.readline.set_completer_delims(self.shell.readline_delims)
313 313
314 def parseline(self, line):
315 if line.startswith("!!"):
316 # Force standard behavior.
317 return super(Pdb, self).parseline(line[2:])
318 # "Smart command mode" from pdb++: don't execute commands if a variable
319 # with the same name exists.
320 cmd, arg, newline = super(Pdb, self).parseline(line)
321 if cmd in self.curframe.f_globals or cmd in self.curframe.f_locals:
322 return super(Pdb, self).parseline("!" + line)
323 return super(Pdb, self).parseline(line)
324
314 325 def new_do_up(self, arg):
315 326 OldPdb.do_up(self, arg)
316 327 self.shell.set_completer_frame(self.curframe)
317 328 do_u = do_up = decorate_fn_with_doc(new_do_up, OldPdb.do_up)
318 329
319 330 def new_do_down(self, arg):
320 331 OldPdb.do_down(self, arg)
321 332 self.shell.set_completer_frame(self.curframe)
322 333
323 334 do_d = do_down = decorate_fn_with_doc(new_do_down, OldPdb.do_down)
324 335
325 336 def new_do_frame(self, arg):
326 337 OldPdb.do_frame(self, arg)
327 338 self.shell.set_completer_frame(self.curframe)
328 339
329 340 def new_do_quit(self, arg):
330 341
331 342 if hasattr(self, 'old_all_completions'):
332 343 self.shell.Completer.all_completions=self.old_all_completions
333 344
334 345 return OldPdb.do_quit(self, arg)
335 346
336 347 do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
337 348
338 349 def new_do_restart(self, arg):
339 350 """Restart command. In the context of ipython this is exactly the same
340 351 thing as 'quit'."""
341 352 self.msg("Restart doesn't make sense here. Using 'quit' instead.")
342 353 return self.do_quit(arg)
343 354
344 355 def postloop(self):
345 356 self.shell.set_completer_frame(None)
346 357
347 358 def print_stack_trace(self, context=None):
348 359 if context is None:
349 360 context = self.context
350 361 try:
351 362 context=int(context)
352 363 if context <= 0:
353 364 raise ValueError("Context must be a positive integer")
354 365 except (TypeError, ValueError):
355 366 raise ValueError("Context must be a positive integer")
356 367 try:
357 368 for frame_lineno in self.stack:
358 369 self.print_stack_entry(frame_lineno, context=context)
359 370 except KeyboardInterrupt:
360 371 pass
361 372
362 373 def print_stack_entry(self,frame_lineno,prompt_prefix='\n-> ',
363 374 context=None):
364 375 if context is None:
365 376 context = self.context
366 377 try:
367 378 context=int(context)
368 379 if context <= 0:
369 380 raise ValueError("Context must be a positive integer")
370 381 except (TypeError, ValueError):
371 382 raise ValueError("Context must be a positive integer")
372 383 print(self.format_stack_entry(frame_lineno, '', context))
373 384
374 385 # vds: >>
375 386 frame, lineno = frame_lineno
376 387 filename = frame.f_code.co_filename
377 388 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
378 389 # vds: <<
379 390
380 391 def format_stack_entry(self, frame_lineno, lprefix=': ', context=None):
381 392 if context is None:
382 393 context = self.context
383 394 try:
384 395 context=int(context)
385 396 if context <= 0:
386 397 print("Context must be a positive integer")
387 398 except (TypeError, ValueError):
388 399 print("Context must be a positive integer")
389 400 try:
390 401 import reprlib # Py 3
391 402 except ImportError:
392 403 import repr as reprlib # Py 2
393 404
394 405 ret = []
395 406
396 407 Colors = self.color_scheme_table.active_colors
397 408 ColorsNormal = Colors.Normal
398 409 tpl_link = u'%s%%s%s' % (Colors.filenameEm, ColorsNormal)
399 410 tpl_call = u'%s%%s%s%%s%s' % (Colors.vName, Colors.valEm, ColorsNormal)
400 411 tpl_line = u'%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
401 412 tpl_line_em = u'%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line,
402 413 ColorsNormal)
403 414
404 415 frame, lineno = frame_lineno
405 416
406 417 return_value = ''
407 418 if '__return__' in frame.f_locals:
408 419 rv = frame.f_locals['__return__']
409 420 #return_value += '->'
410 421 return_value += reprlib.repr(rv) + '\n'
411 422 ret.append(return_value)
412 423
413 424 #s = filename + '(' + `lineno` + ')'
414 425 filename = self.canonic(frame.f_code.co_filename)
415 426 link = tpl_link % py3compat.cast_unicode(filename)
416 427
417 428 if frame.f_code.co_name:
418 429 func = frame.f_code.co_name
419 430 else:
420 431 func = "<lambda>"
421 432
422 433 call = ''
423 434 if func != '?':
424 435 if '__args__' in frame.f_locals:
425 436 args = reprlib.repr(frame.f_locals['__args__'])
426 437 else:
427 438 args = '()'
428 439 call = tpl_call % (func, args)
429 440
430 441 # The level info should be generated in the same format pdb uses, to
431 442 # avoid breaking the pdbtrack functionality of python-mode in *emacs.
432 443 if frame is self.curframe:
433 444 ret.append('> ')
434 445 else:
435 446 ret.append(' ')
436 447 ret.append(u'%s(%s)%s\n' % (link,lineno,call))
437 448
438 449 start = lineno - 1 - context//2
439 450 lines = ulinecache.getlines(filename)
440 451 start = min(start, len(lines) - context)
441 452 start = max(start, 0)
442 453 lines = lines[start : start + context]
443 454
444 455 for i,line in enumerate(lines):
445 456 show_arrow = (start + 1 + i == lineno)
446 457 linetpl = (frame is self.curframe or show_arrow) \
447 458 and tpl_line_em \
448 459 or tpl_line
449 460 ret.append(self.__format_line(linetpl, filename,
450 461 start + 1 + i, line,
451 462 arrow = show_arrow) )
452 463 return ''.join(ret)
453 464
454 465 def __format_line(self, tpl_line, filename, lineno, line, arrow = False):
455 466 bp_mark = ""
456 467 bp_mark_color = ""
457 468
458 469 scheme = self.color_scheme_table.active_scheme_name
459 470 new_line, err = self.parser.format2(line, 'str', scheme)
460 471 if not err: line = new_line
461 472
462 473 bp = None
463 474 if lineno in self.get_file_breaks(filename):
464 475 bps = self.get_breaks(filename, lineno)
465 476 bp = bps[-1]
466 477
467 478 if bp:
468 479 Colors = self.color_scheme_table.active_colors
469 480 bp_mark = str(bp.number)
470 481 bp_mark_color = Colors.breakpoint_enabled
471 482 if not bp.enabled:
472 483 bp_mark_color = Colors.breakpoint_disabled
473 484
474 485 numbers_width = 7
475 486 if arrow:
476 487 # This is the line with the error
477 488 pad = numbers_width - len(str(lineno)) - len(bp_mark)
478 489 num = '%s%s' % (make_arrow(pad), str(lineno))
479 490 else:
480 491 num = '%*s' % (numbers_width - len(bp_mark), str(lineno))
481 492
482 493 return tpl_line % (bp_mark_color + bp_mark, num, line)
483 494
484 495
485 496 def list_command_pydb(self, arg):
486 497 """List command to use if we have a newer pydb installed"""
487 498 filename, first, last = OldPdb.parse_list_cmd(self, arg)
488 499 if filename is not None:
489 500 self.print_list_lines(filename, first, last)
490 501
491 502 def print_list_lines(self, filename, first, last):
492 503 """The printing (as opposed to the parsing part of a 'list'
493 504 command."""
494 505 try:
495 506 Colors = self.color_scheme_table.active_colors
496 507 ColorsNormal = Colors.Normal
497 508 tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
498 509 tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal)
499 510 src = []
500 511 if filename == "<string>" and hasattr(self, "_exec_filename"):
501 512 filename = self._exec_filename
502 513
503 514 for lineno in range(first, last+1):
504 515 line = ulinecache.getline(filename, lineno)
505 516 if not line:
506 517 break
507 518
508 519 if lineno == self.curframe.f_lineno:
509 520 line = self.__format_line(tpl_line_em, filename, lineno, line, arrow = True)
510 521 else:
511 522 line = self.__format_line(tpl_line, filename, lineno, line, arrow = False)
512 523
513 524 src.append(line)
514 525 self.lineno = lineno
515 526
516 527 print(''.join(src))
517 528
518 529 except KeyboardInterrupt:
519 530 pass
520 531
521 532 def do_list(self, arg):
522 533 self.lastcmd = 'list'
523 534 last = None
524 535 if arg:
525 536 try:
526 537 x = eval(arg, {}, {})
527 538 if type(x) == type(()):
528 539 first, last = x
529 540 first = int(first)
530 541 last = int(last)
531 542 if last < first:
532 543 # Assume it's a count
533 544 last = first + last
534 545 else:
535 546 first = max(1, int(x) - 5)
536 547 except:
537 548 print('*** Error in argument:', repr(arg))
538 549 return
539 550 elif self.lineno is None:
540 551 first = max(1, self.curframe.f_lineno - 5)
541 552 else:
542 553 first = self.lineno + 1
543 554 if last is None:
544 555 last = first + 10
545 556 self.print_list_lines(self.curframe.f_code.co_filename, first, last)
546 557
547 558 # vds: >>
548 559 lineno = first
549 560 filename = self.curframe.f_code.co_filename
550 561 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
551 562 # vds: <<
552 563
553 564 do_l = do_list
554 565
555 566 def getsourcelines(self, obj):
556 567 lines, lineno = inspect.findsource(obj)
557 568 if inspect.isframe(obj) and obj.f_globals is obj.f_locals:
558 569 # must be a module frame: do not try to cut a block out of it
559 570 return lines, 1
560 571 elif inspect.ismodule(obj):
561 572 return lines, 1
562 573 return inspect.getblock(lines[lineno:]), lineno+1
563 574
564 575 def do_longlist(self, arg):
565 576 self.lastcmd = 'longlist'
566 577 try:
567 578 lines, lineno = self.getsourcelines(self.curframe)
568 579 except OSError as err:
569 580 self.error(err)
570 581 return
571 582 last = lineno + len(lines)
572 583 self.print_list_lines(self.curframe.f_code.co_filename, lineno, last)
573 584 do_ll = do_longlist
574 585
575 586 def do_pdef(self, arg):
576 587 """Print the call signature for any callable object.
577 588
578 589 The debugger interface to %pdef"""
579 590 namespaces = [('Locals', self.curframe.f_locals),
580 591 ('Globals', self.curframe.f_globals)]
581 592 self.shell.find_line_magic('pdef')(arg, namespaces=namespaces)
582 593
583 594 def do_pdoc(self, arg):
584 595 """Print the docstring for an object.
585 596
586 597 The debugger interface to %pdoc."""
587 598 namespaces = [('Locals', self.curframe.f_locals),
588 599 ('Globals', self.curframe.f_globals)]
589 600 self.shell.find_line_magic('pdoc')(arg, namespaces=namespaces)
590 601
591 602 def do_pfile(self, arg):
592 603 """Print (or run through pager) the file where an object is defined.
593 604
594 605 The debugger interface to %pfile.
595 606 """
596 607 namespaces = [('Locals', self.curframe.f_locals),
597 608 ('Globals', self.curframe.f_globals)]
598 609 self.shell.find_line_magic('pfile')(arg, namespaces=namespaces)
599 610
600 611 def do_pinfo(self, arg):
601 612 """Provide detailed information about an object.
602 613
603 614 The debugger interface to %pinfo, i.e., obj?."""
604 615 namespaces = [('Locals', self.curframe.f_locals),
605 616 ('Globals', self.curframe.f_globals)]
606 617 self.shell.find_line_magic('pinfo')(arg, namespaces=namespaces)
607 618
608 619 def do_pinfo2(self, arg):
609 620 """Provide extra detailed information about an object.
610 621
611 622 The debugger interface to %pinfo2, i.e., obj??."""
612 623 namespaces = [('Locals', self.curframe.f_locals),
613 624 ('Globals', self.curframe.f_globals)]
614 625 self.shell.find_line_magic('pinfo2')(arg, namespaces=namespaces)
615 626
616 627 def do_psource(self, arg):
617 628 """Print (or run through pager) the source code for an object."""
618 629 namespaces = [('Locals', self.curframe.f_locals),
619 630 ('Globals', self.curframe.f_globals)]
620 631 self.shell.find_line_magic('psource')(arg, namespaces=namespaces)
621 632
622 633 if sys.version_info > (3, ):
623 634 def do_where(self, arg):
624 635 """w(here)
625 636 Print a stack trace, with the most recent frame at the bottom.
626 637 An arrow indicates the "current frame", which determines the
627 638 context of most commands. 'bt' is an alias for this command.
628 639
629 640 Take a number as argument as an (optional) number of context line to
630 641 print"""
631 642 if arg:
632 643 context = int(arg)
633 644 self.print_stack_trace(context)
634 645 else:
635 646 self.print_stack_trace()
636 647
637 648 do_w = do_where
General Comments 0
You need to be logged in to leave comments. Login now