blob: 7cf077172313ab1efed87eb47ff0752d71489d65 [file] [log] [blame]
Ka-Ping Yee6397c7c2001-02-27 14:43:21 +00001"""Get useful information from live Python objects.
2
3This module encapsulates the interface provided by the internal special
4attributes (func_*, co_*, im_*, tb_*, etc.) in a friendlier fashion.
5It also provides some help for examining source code and class layout.
6
7Here are some of the useful functions provided by this module:
8
9 ismodule(), isclass(), ismethod(), isfunction(), istraceback(),
10 isframe(), iscode(), isbuiltin(), isroutine() - check object types
11 getmembers() - get members of an object that satisfy a given condition
12
13 getfile(), getsourcefile(), getsource() - find an object's source code
14 getdoc(), getcomments() - get documentation on an object
15 getmodule() - determine the module that an object came from
16 getclasstree() - arrange classes so as to represent their hierarchy
17
18 getargspec(), getargvalues() - get info about function arguments
19 formatargspec(), formatargvalues() - format an argument spec
20 getouterframes(), getinnerframes() - get info about frames
21 currentframe() - get the current stack frame
22 stack(), trace() - get info about frames on the stack or in a traceback
23"""
24
25# This module is in the public domain. No warranties.
26
Ka-Ping Yee8b58b842001-03-01 13:56:16 +000027__author__ = 'Ka-Ping Yee <ping@lfw.org>'
28__date__ = '1 Jan 2001'
Ka-Ping Yee6397c7c2001-02-27 14:43:21 +000029
Ka-Ping Yee7a257652001-03-02 01:19:39 +000030import sys, os, types, string, dis, imp, tokenize
Ka-Ping Yee6397c7c2001-02-27 14:43:21 +000031
32# ----------------------------------------------------------- type-checking
33def ismodule(object):
34 """Return true if the object is a module.
35
36 Module objects provide these attributes:
37 __doc__ documentation string
38 __file__ filename (missing for built-in modules)"""
39 return type(object) is types.ModuleType
40
41def isclass(object):
42 """Return true if the object is a class.
43
44 Class objects provide these attributes:
45 __doc__ documentation string
46 __module__ name of module in which this class was defined"""
47 return type(object) is types.ClassType
48
49def ismethod(object):
50 """Return true if the object is an instance method.
51
52 Instance method objects provide these attributes:
53 __doc__ documentation string
54 __name__ name with which this method was defined
55 im_class class object in which this method belongs
56 im_func function object containing implementation of method
57 im_self instance to which this method is bound, or None"""
58 return type(object) is types.MethodType
59
60def isfunction(object):
61 """Return true if the object is a user-defined function.
62
63 Function objects provide these attributes:
64 __doc__ documentation string
65 __name__ name with which this function was defined
66 func_code code object containing compiled function bytecode
67 func_defaults tuple of any default values for arguments
68 func_doc (same as __doc__)
69 func_globals global namespace in which this function was defined
70 func_name (same as __name__)"""
71 return type(object) in [types.FunctionType, types.LambdaType]
72
73def istraceback(object):
74 """Return true if the object is a traceback.
75
76 Traceback objects provide these attributes:
77 tb_frame frame object at this level
78 tb_lasti index of last attempted instruction in bytecode
79 tb_lineno current line number in Python source code
80 tb_next next inner traceback object (called by this level)"""
81 return type(object) is types.TracebackType
82
83def isframe(object):
84 """Return true if the object is a frame object.
85
86 Frame objects provide these attributes:
87 f_back next outer frame object (this frame's caller)
88 f_builtins built-in namespace seen by this frame
89 f_code code object being executed in this frame
90 f_exc_traceback traceback if raised in this frame, or None
91 f_exc_type exception type if raised in this frame, or None
92 f_exc_value exception value if raised in this frame, or None
93 f_globals global namespace seen by this frame
94 f_lasti index of last attempted instruction in bytecode
95 f_lineno current line number in Python source code
96 f_locals local namespace seen by this frame
97 f_restricted 0 or 1 if frame is in restricted execution mode
98 f_trace tracing function for this frame, or None"""
99 return type(object) is types.FrameType
100
101def iscode(object):
102 """Return true if the object is a code object.
103
104 Code objects provide these attributes:
105 co_argcount number of arguments (not including * or ** args)
106 co_code string of raw compiled bytecode
107 co_consts tuple of constants used in the bytecode
108 co_filename name of file in which this code object was created
109 co_firstlineno number of first line in Python source code
110 co_flags bitmap: 1=optimized | 2=newlocals | 4=*arg | 8=**arg
111 co_lnotab encoded mapping of line numbers to bytecode indices
112 co_name name with which this code object was defined
113 co_names tuple of names of local variables
114 co_nlocals number of local variables
115 co_stacksize virtual machine stack space required
116 co_varnames tuple of names of arguments and local variables"""
117 return type(object) is types.CodeType
118
119def isbuiltin(object):
120 """Return true if the object is a built-in function or method.
121
122 Built-in functions and methods provide these attributes:
123 __doc__ documentation string
124 __name__ original name of this function or method
125 __self__ instance to which a method is bound, or None"""
126 return type(object) in [types.BuiltinFunctionType,
127 types.BuiltinMethodType]
128
129def isroutine(object):
130 """Return true if the object is any kind of function or method."""
131 return type(object) in [types.FunctionType, types.LambdaType,
132 types.MethodType, types.BuiltinFunctionType,
133 types.BuiltinMethodType]
134
135def getmembers(object, predicate=None):
136 """Return all members of an object as (name, value) pairs sorted by name.
137 Optionally, only return members that satisfy a given predicate."""
138 results = []
139 for key in dir(object):
140 value = getattr(object, key)
141 if not predicate or predicate(value):
142 results.append((key, value))
143 results.sort()
144 return results
145
146# -------------------------------------------------- source code extraction
147def indentsize(line):
148 """Return the indent size, in spaces, at the start of a line of text."""
149 expline = string.expandtabs(line)
150 return len(expline) - len(string.lstrip(expline))
151
152def getdoc(object):
153 """Get the documentation string for an object.
154
155 All tabs are expanded to spaces. To clean up docstrings that are
156 indented to line up with blocks of code, any whitespace than can be
157 uniformly removed from the second line onwards is removed."""
158 if hasattr(object, '__doc__') and object.__doc__:
159 lines = string.split(string.expandtabs(object.__doc__), '\n')
160 margin = None
161 for line in lines[1:]:
162 content = len(string.lstrip(line))
163 if not content: continue
164 indent = len(line) - content
165 if margin is None: margin = indent
166 else: margin = min(margin, indent)
167 if margin is not None:
168 for i in range(1, len(lines)): lines[i] = lines[i][margin:]
169 return string.join(lines, '\n')
170
171def getfile(object):
Ka-Ping Yeec113c242001-03-02 02:08:53 +0000172 """Work out which source or compiled file an object was defined in."""
Ka-Ping Yee6397c7c2001-02-27 14:43:21 +0000173 if ismodule(object):
174 if hasattr(object, '__file__'):
175 return object.__file__
176 raise TypeError, 'arg is a built-in module'
177 if isclass(object):
178 object = sys.modules[object.__module__]
179 if hasattr(object, '__file__'):
180 return object.__file__
181 raise TypeError, 'arg is a built-in class'
182 if ismethod(object):
183 object = object.im_func
184 if isfunction(object):
185 object = object.func_code
186 if istraceback(object):
187 object = object.tb_frame
188 if isframe(object):
189 object = object.f_code
190 if iscode(object):
191 return object.co_filename
192 raise TypeError, 'arg is not a module, class, method, ' \
193 'function, traceback, frame, or code object'
194
Ka-Ping Yeec113c242001-03-02 02:08:53 +0000195def getsourcefile(object):
196 """Return the Python source file an object was defined in, if it exists."""
197 filename = getfile(object)
198 if string.lower(filename[-4:]) in ['.pyc', '.pyo']:
199 filename = filename[:-4] + '.py'
200 if string.lower(filename[-3:]) == '.py' and os.path.exists(filename):
201 return filename
202
203def getabsfile(object):
204 """Return an absolute path to the source file or compiled file for an object.
205
206 The idea is for each object to have a unique origin, so this routine normalizes
207 the result as much as possible."""
208 return os.path.normcase(os.path.abspath(getsourcefile(object) or getfile(object)))
209
Ka-Ping Yee6397c7c2001-02-27 14:43:21 +0000210modulesbyfile = {}
211
212def getmodule(object):
213 """Try to guess which module an object was defined in."""
214 if isclass(object):
Ka-Ping Yee8b58b842001-03-01 13:56:16 +0000215 return sys.modules.get(object.__module__)
Ka-Ping Yee6397c7c2001-02-27 14:43:21 +0000216 try:
Ka-Ping Yeec113c242001-03-02 02:08:53 +0000217 file = getabsfile(object)
Ka-Ping Yee6397c7c2001-02-27 14:43:21 +0000218 except TypeError:
219 return None
220 if modulesbyfile.has_key(file):
221 return sys.modules[modulesbyfile[file]]
222 for module in sys.modules.values():
223 if hasattr(module, '__file__'):
Ka-Ping Yeec113c242001-03-02 02:08:53 +0000224 modulesbyfile[getabsfile(module)] = module.__name__
Ka-Ping Yee6397c7c2001-02-27 14:43:21 +0000225 if modulesbyfile.has_key(file):
226 return sys.modules[modulesbyfile[file]]
227 main = sys.modules['__main__']
228 try:
229 mainobject = getattr(main, object.__name__)
230 if mainobject is object: return main
231 except AttributeError: pass
232 builtin = sys.modules['__builtin__']
233 try:
234 builtinobject = getattr(builtin, object.__name__)
235 if builtinobject is object: return builtin
236 except AttributeError: pass
237
Ka-Ping Yee6397c7c2001-02-27 14:43:21 +0000238def findsource(object):
239 """Return the entire source file and starting line number for an object.
240
241 The argument may be a module, class, method, function, traceback, frame,
242 or code object. The source code is returned as a list of all the lines
243 in the file and the line number indexes a line in that list. An IOError
244 is raised if the source code cannot be retrieved."""
245 try:
246 file = open(getsourcefile(object))
247 lines = file.readlines()
248 file.close()
249 except (TypeError, IOError):
250 raise IOError, 'could not get source code'
251
252 if ismodule(object):
253 return lines, 0
254
255 if isclass(object):
256 name = object.__name__
257 matches = (['class', name], ['class', name + ':'])
258 for i in range(len(lines)):
259 if string.split(lines[i])[:2] in matches:
260 return lines, i
261 else: raise IOError, 'could not find class definition'
262
263 if ismethod(object):
264 object = object.im_func
265 if isfunction(object):
266 object = object.func_code
267 if istraceback(object):
268 object = object.tb_frame
269 if isframe(object):
270 object = object.f_code
271 if iscode(object):
272 try:
273 lnum = object.co_firstlineno - 1
274 except AttributeError:
275 raise IOError, 'could not find function definition'
276 else:
277 while lnum > 0:
278 if string.split(lines[lnum])[:1] == ['def']: break
279 lnum = lnum - 1
280 return lines, lnum
281
282def getcomments(object):
283 """Get lines of comments immediately preceding an object's source code."""
284 try: lines, lnum = findsource(object)
285 except: return None
286
287 if ismodule(object):
288 # Look for a comment block at the top of the file.
289 start = 0
290 if lines[0][:2] == '#!': start = 1
291 while start < len(lines) and string.strip(lines[start]) in ['', '#']:
292 start = start + 1
293 if lines[start][:1] == '#':
294 comments = []
295 end = start
296 while end < len(lines) and lines[end][:1] == '#':
297 comments.append(string.expandtabs(lines[end]))
298 end = end + 1
299 return string.join(comments, '')
300
301 # Look for a preceding block of comments at the same indentation.
302 elif lnum > 0:
303 indent = indentsize(lines[lnum])
304 end = lnum - 1
305 if end >= 0 and string.lstrip(lines[end])[:1] == '#' and \
306 indentsize(lines[end]) == indent:
307 comments = [string.lstrip(string.expandtabs(lines[end]))]
308 if end > 0:
309 end = end - 1
310 comment = string.lstrip(string.expandtabs(lines[end]))
311 while comment[:1] == '#' and indentsize(lines[end]) == indent:
312 comments[:0] = [comment]
313 end = end - 1
314 if end < 0: break
315 comment = string.lstrip(string.expandtabs(lines[end]))
316 while comments and string.strip(comments[0]) == '#':
317 comments[:1] = []
318 while comments and string.strip(comments[-1]) == '#':
319 comments[-1:] = []
320 return string.join(comments, '')
321
322class ListReader:
323 """Provide a readline() method to return lines from a list of strings."""
324 def __init__(self, lines):
325 self.lines = lines
326 self.index = 0
327
328 def readline(self):
329 i = self.index
330 if i < len(self.lines):
331 self.index = i + 1
332 return self.lines[i]
333 else: return ''
334
335class EndOfBlock(Exception): pass
336
337class BlockFinder:
338 """Provide a tokeneater() method to detect the end of a code block."""
339 def __init__(self):
340 self.indent = 0
341 self.started = 0
342 self.last = 0
343
344 def tokeneater(self, type, token, (srow, scol), (erow, ecol), line):
345 if not self.started:
346 if type == tokenize.NAME: self.started = 1
347 elif type == tokenize.NEWLINE:
348 self.last = srow
349 elif type == tokenize.INDENT:
350 self.indent = self.indent + 1
351 elif type == tokenize.DEDENT:
352 self.indent = self.indent - 1
353 if self.indent == 0: raise EndOfBlock, self.last
354
355def getblock(lines):
356 """Extract the block of code at the top of the given list of lines."""
357 try:
358 tokenize.tokenize(ListReader(lines).readline, BlockFinder().tokeneater)
359 except EndOfBlock, eob:
360 return lines[:eob.args[0]]
361
362def getsourcelines(object):
363 """Return a list of source lines and starting line number for an object.
364
365 The argument may be a module, class, method, function, traceback, frame,
366 or code object. The source code is returned as a list of the lines
367 corresponding to the object and the line number indicates where in the
368 original source file the first line of code was found. An IOError is
369 raised if the source code cannot be retrieved."""
370 lines, lnum = findsource(object)
371
372 if ismodule(object): return lines, 0
373 else: return getblock(lines[lnum:]), lnum + 1
374
375def getsource(object):
376 """Return the text of the source code for an object.
377
378 The argument may be a module, class, method, function, traceback, frame,
379 or code object. The source code is returned as a single string. An
380 IOError is raised if the source code cannot be retrieved."""
381 lines, lnum = getsourcelines(object)
382 return string.join(lines, '')
383
384# --------------------------------------------------- class tree extraction
385def walktree(classes, children, parent):
386 """Recursive helper function for getclasstree()."""
387 results = []
388 classes.sort(lambda a, b: cmp(a.__name__, b.__name__))
389 for c in classes:
390 results.append((c, c.__bases__))
391 if children.has_key(c):
392 results.append(walktree(children[c], children, c))
393 return results
394
395def getclasstree(classes, unique=0):
396 """Arrange the given list of classes into a hierarchy of nested lists.
397
398 Where a nested list appears, it contains classes derived from the class
399 whose entry immediately precedes the list. Each entry is a 2-tuple
400 containing a class and a tuple of its base classes. If the 'unique'
401 argument is true, exactly one entry appears in the returned structure
402 for each class in the given list. Otherwise, classes using multiple
403 inheritance and their descendants will appear multiple times."""
404 children = {}
405 roots = []
406 for c in classes:
407 if c.__bases__:
408 for parent in c.__bases__:
409 if not children.has_key(parent):
410 children[parent] = []
411 children[parent].append(c)
412 if unique and parent in classes: break
413 elif c not in roots:
414 roots.append(c)
415 for parent in children.keys():
416 if parent not in classes:
417 roots.append(parent)
418 return walktree(roots, children, None)
419
420# ------------------------------------------------ argument list extraction
421# These constants are from Python's compile.h.
422CO_OPTIMIZED, CO_NEWLOCALS, CO_VARARGS, CO_VARKEYWORDS = 1, 2, 4, 8
423
424def getargs(co):
425 """Get information about the arguments accepted by a code object.
426
427 Three things are returned: (args, varargs, varkw), where 'args' is
428 a list of argument names (possibly containing nested lists), and
429 'varargs' and 'varkw' are the names of the * and ** arguments or None."""
430 if not iscode(co): raise TypeError, 'arg is not a code object'
431
432 code = co.co_code
433 nargs = co.co_argcount
434 names = co.co_varnames
435 args = list(names[:nargs])
436 step = 0
437
438 # The following acrobatics are for anonymous (tuple) arguments.
439 for i in range(nargs):
440 if args[i][:1] in ['', '.']:
441 stack, remain, count = [], [], []
442 while step < len(code):
443 op = ord(code[step])
444 step = step + 1
445 if op >= dis.HAVE_ARGUMENT:
446 opname = dis.opname[op]
447 value = ord(code[step]) + ord(code[step+1])*256
448 step = step + 2
449 if opname in ['UNPACK_TUPLE', 'UNPACK_SEQUENCE']:
450 remain.append(value)
451 count.append(value)
452 elif opname == 'STORE_FAST':
453 stack.append(names[value])
454 remain[-1] = remain[-1] - 1
455 while remain[-1] == 0:
456 remain.pop()
457 size = count.pop()
458 stack[-size:] = [stack[-size:]]
459 if not remain: break
460 remain[-1] = remain[-1] - 1
461 if not remain: break
462 args[i] = stack[0]
463
464 varargs = None
465 if co.co_flags & CO_VARARGS:
466 varargs = co.co_varnames[nargs]
467 nargs = nargs + 1
468 varkw = None
469 if co.co_flags & CO_VARKEYWORDS:
470 varkw = co.co_varnames[nargs]
471 return args, varargs, varkw
472
473def getargspec(func):
474 """Get the names and default values of a function's arguments.
475
476 A tuple of four things is returned: (args, varargs, varkw, defaults).
477 'args' is a list of the argument names (it may contain nested lists).
478 'varargs' and 'varkw' are the names of the * and ** arguments or None.
479 'defaults' is an n-tuple of the default values of the last n arguments."""
480 if not isfunction(func): raise TypeError, 'arg is not a Python function'
481 args, varargs, varkw = getargs(func.func_code)
482 return args, varargs, varkw, func.func_defaults
483
484def getargvalues(frame):
485 """Get information about arguments passed into a particular frame.
486
487 A tuple of four things is returned: (args, varargs, varkw, locals).
488 'args' is a list of the argument names (it may contain nested lists).
489 'varargs' and 'varkw' are the names of the * and ** arguments or None.
490 'locals' is the locals dictionary of the given frame."""
491 args, varargs, varkw = getargs(frame.f_code)
492 return args, varargs, varkw, frame.f_locals
493
494def joinseq(seq):
495 if len(seq) == 1:
496 return '(' + seq[0] + ',)'
497 else:
498 return '(' + string.join(seq, ', ') + ')'
499
500def strseq(object, convert, join=joinseq):
501 """Recursively walk a sequence, stringifying each element."""
502 if type(object) in [types.ListType, types.TupleType]:
503 return join(map(lambda o, c=convert, j=join: strseq(o, c, j), object))
504 else:
505 return convert(object)
506
507def formatargspec(args, varargs=None, varkw=None, defaults=None,
508 formatarg=str,
509 formatvarargs=lambda name: '*' + name,
510 formatvarkw=lambda name: '**' + name,
511 formatvalue=lambda value: '=' + repr(value),
512 join=joinseq):
513 """Format an argument spec from the 4 values returned by getargspec.
514
515 The first four arguments are (args, varargs, varkw, defaults). The
516 other four arguments are the corresponding optional formatting functions
517 that are called to turn names and values into strings. The ninth
518 argument is an optional function to format the sequence of arguments."""
519 specs = []
520 if defaults:
521 firstdefault = len(args) - len(defaults)
522 for i in range(len(args)):
523 spec = strseq(args[i], formatarg, join)
524 if defaults and i >= firstdefault:
525 spec = spec + formatvalue(defaults[i - firstdefault])
526 specs.append(spec)
527 if varargs:
528 specs.append(formatvarargs(varargs))
529 if varkw:
530 specs.append(formatvarkw(varkw))
531 return '(' + string.join(specs, ', ') + ')'
532
533def formatargvalues(args, varargs, varkw, locals,
534 formatarg=str,
535 formatvarargs=lambda name: '*' + name,
536 formatvarkw=lambda name: '**' + name,
537 formatvalue=lambda value: '=' + repr(value),
538 join=joinseq):
539 """Format an argument spec from the 4 values returned by getargvalues.
540
541 The first four arguments are (args, varargs, varkw, locals). The
542 next four arguments are the corresponding optional formatting functions
543 that are called to turn names and values into strings. The ninth
544 argument is an optional function to format the sequence of arguments."""
545 def convert(name, locals=locals,
546 formatarg=formatarg, formatvalue=formatvalue):
547 return formatarg(name) + formatvalue(locals[name])
548 specs = []
549 for i in range(len(args)):
550 specs.append(strseq(args[i], convert, join))
551 if varargs:
552 specs.append(formatvarargs(varargs) + formatvalue(locals[varargs]))
553 if varkw:
554 specs.append(formatvarkw(varkw) + formatvalue(locals[varkw]))
555 return '(' + string.join(specs, ', ') + ')'
556
557# -------------------------------------------------- stack frame extraction
558def getframeinfo(frame, context=1):
559 """Get information about a frame or traceback object.
560
561 A tuple of five things is returned: the filename, the line number of
562 the current line, the function name, a list of lines of context from
563 the source code, and the index of the current line within that list.
564 The optional second argument specifies the number of lines of context
565 to return, which are centered around the current line."""
566 if istraceback(frame):
567 frame = frame.tb_frame
568 if not isframe(frame):
569 raise TypeError, 'arg is not a frame or traceback object'
570
571 filename = getsourcefile(frame)
Ka-Ping Yee59ade082001-03-01 03:55:35 +0000572 lineno = getlineno(frame)
Ka-Ping Yee6397c7c2001-02-27 14:43:21 +0000573 if context > 0:
Ka-Ping Yee59ade082001-03-01 03:55:35 +0000574 start = lineno - 1 - context/2
Ka-Ping Yee6397c7c2001-02-27 14:43:21 +0000575 try:
576 lines, lnum = findsource(frame)
577 start = max(start, 1)
578 start = min(start, len(lines) - context)
579 lines = lines[start:start+context]
Ka-Ping Yee59ade082001-03-01 03:55:35 +0000580 index = lineno - 1 - start
Ka-Ping Yeec113c242001-03-02 02:08:53 +0000581 except IOError:
Ka-Ping Yee6397c7c2001-02-27 14:43:21 +0000582 lines = index = None
583 else:
584 lines = index = None
585
Ka-Ping Yee59ade082001-03-01 03:55:35 +0000586 return (filename, lineno, frame.f_code.co_name, lines, index)
587
588def getlineno(frame):
589 """Get the line number from a frame object, allowing for optimization."""
590 # Written by Marc-André Lemburg; revised by Jim Hugunin and Fredrik Lundh.
591 lineno = frame.f_lineno
592 code = frame.f_code
593 if hasattr(code, 'co_lnotab'):
594 table = code.co_lnotab
595 lineno = code.co_firstlineno
596 addr = 0
597 for i in range(0, len(table), 2):
598 addr = addr + ord(table[i])
599 if addr > frame.f_lasti: break
600 lineno = lineno + ord(table[i+1])
601 return lineno
Ka-Ping Yee6397c7c2001-02-27 14:43:21 +0000602
603def getouterframes(frame, context=1):
604 """Get a list of records for a frame and all higher (calling) frames.
605
606 Each record contains a frame object, filename, line number, function
607 name, a list of lines of context, and index within the context."""
608 framelist = []
609 while frame:
610 framelist.append((frame,) + getframeinfo(frame, context))
611 frame = frame.f_back
612 return framelist
613
614def getinnerframes(tb, context=1):
615 """Get a list of records for a traceback's frame and all lower frames.
616
617 Each record contains a frame object, filename, line number, function
618 name, a list of lines of context, and index within the context."""
619 tb = tb.tb_next
620 framelist = []
621 while tb:
622 framelist.append((tb.tb_frame,) + getframeinfo(tb, context))
623 tb = tb.tb_next
624 return framelist
625
626def currentframe():
627 """Return the frame object for the caller's stack frame."""
628 try:
629 raise 'catch me'
630 except:
631 return sys.exc_traceback.tb_frame.f_back
632
633if hasattr(sys, '_getframe'): currentframe = sys._getframe
634
635def stack(context=1):
636 """Return a list of records for the stack above the caller's frame."""
637 return getouterframes(currentframe().f_back, context)
638
639def trace(context=1):
Tim Peters85ba6732001-02-28 08:26:44 +0000640 """Return a list of records for the stack below the current exception."""
Ka-Ping Yee6397c7c2001-02-27 14:43:21 +0000641 return getinnerframes(sys.exc_traceback, context)
Ka-Ping Yee8b58b842001-03-01 13:56:16 +0000642
643