summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorcsabella <chekat2@gmail.com>2017-05-16 22:28:02 (GMT)
committerterryjreedy <tjreedy@udel.edu>2017-05-16 22:28:02 (GMT)
commit0774e79b93cc494b3a957d538c7c112e289973c0 (patch)
tree8e95bd91d1d5c4735704a6792f2f83ab10c99d89
parent4ae01496971624c75080431806ed1c08e00f22c7 (diff)
downloadcpython-0774e79b93cc494b3a957d538c7c112e289973c0.zip
cpython-0774e79b93cc494b3a957d538c7c112e289973c0.tar.gz
cpython-0774e79b93cc494b3a957d538c7c112e289973c0.tar.bz2
bpo-30211: bdb: add docstrings (#1350)
-rw-r--r--Lib/bdb.py230
1 files changed, 212 insertions, 18 deletions
diff --git a/Lib/bdb.py b/Lib/bdb.py
index 5a80fa8..eb2fa4f 100644
--- a/Lib/bdb.py
+++ b/Lib/bdb.py
@@ -7,6 +7,7 @@ from inspect import CO_GENERATOR
__all__ = ["BdbQuit", "Bdb", "Breakpoint"]
+
class BdbQuit(Exception):
"""Exception to give up completely."""
@@ -17,6 +18,12 @@ class Bdb:
This class takes care of details of the trace facility;
a derived class should implement user interaction.
The standard debugger class (pdb.Pdb) is an example.
+
+ The optional skip argument must be an iterable of glob-style
+ module name patterns. The debugger will not step into frames
+ that originate in a module that matches one of these patterns.
+ Whether a frame is considered to originate in a certain module
+ is determined by the __name__ in the frame globals.
"""
def __init__(self, skip=None):
@@ -26,6 +33,13 @@ class Bdb:
self.frame_returning = None
def canonic(self, filename):
+ """Return canonical form of filename.
+
+ For real filenames, the canonical form is a case-normalized (on
+ case insenstive filesystems) absolute path. 'Filenames' with
+ angle brackets, such as "<stdin>", generated in interactive
+ mode, are returned unchanged.
+ """
if filename == "<" + filename[1:-1] + ">":
return filename
canonic = self.fncache.get(filename)
@@ -36,12 +50,36 @@ class Bdb:
return canonic
def reset(self):
+ """Set values of attributes as ready to start debugging."""
import linecache
linecache.checkcache()
self.botframe = None
self._set_stopinfo(None, None)
def trace_dispatch(self, frame, event, arg):
+ """Dispatch a trace function for debugged frames based on the event.
+
+ This function is installed as the trace function for debugged
+ frames. Its return value is the new trace function, which is
+ usually itself. The default implementation decides how to
+ dispatch a frame, depending on the type of event (passed in as a
+ string) that is about to be executed.
+
+ The event can be one of the following:
+ line: A new line of code is going to be executed.
+ call: A function is about to be called or another code block
+ is entered.
+ return: A function or other code block is about to return.
+ exception: An exception has occurred.
+ c_call: A C function is about to be called.
+ c_return: A C functon has returned.
+ c_exception: A C function has raised an exception.
+
+ For the Python events, specialized functions (see the dispatch_*()
+ methods) are called. For the C events, no action is taken.
+
+ The arg parameter depends on the previous event.
+ """
if self.quitting:
return # None
if event == 'line':
@@ -62,12 +100,24 @@ class Bdb:
return self.trace_dispatch
def dispatch_line(self, frame):
+ """Invoke user function and return trace function for line event.
+
+ If the debugger stops on the current line, invoke
+ self.user_line(). Raise BdbQuit if self.quitting is set.
+ Return self.trace_dispatch to continue tracing in this scope.
+ """
if self.stop_here(frame) or self.break_here(frame):
self.user_line(frame)
if self.quitting: raise BdbQuit
return self.trace_dispatch
def dispatch_call(self, frame, arg):
+ """Invoke user function and return trace function for call event.
+
+ If the debugger stops on this function call, invoke
+ self.user_call(). Raise BbdQuit if self.quitting is set.
+ Return self.trace_dispatch to continue tracing in this scope.
+ """
# XXX 'arg' is no longer used
if self.botframe is None:
# First call of dispatch since reset()
@@ -84,6 +134,12 @@ class Bdb:
return self.trace_dispatch
def dispatch_return(self, frame, arg):
+ """Invoke user function and return trace function for return event.
+
+ If the debugger stops on this function return, invoke
+ self.user_return(). Raise BdbQuit if self.quitting is set.
+ Return self.trace_dispatch to continue tracing in this scope.
+ """
if self.stop_here(frame) or frame == self.returnframe:
# Ignore return events in generator except when stepping.
if self.stopframe and frame.f_code.co_flags & CO_GENERATOR:
@@ -100,6 +156,12 @@ class Bdb:
return self.trace_dispatch
def dispatch_exception(self, frame, arg):
+ """Invoke user function and return trace function for exception event.
+
+ If the debugger stops on this exception, invoke
+ self.user_exception(). Raise BdbQuit if self.quitting is set.
+ Return self.trace_dispatch to continue tracing in this scope.
+ """
if self.stop_here(frame):
# When stepping with next/until/return in a generator frame, skip
# the internal StopIteration exception (with no traceback)
@@ -125,12 +187,14 @@ class Bdb:
# definition of stopping and breakpoints.
def is_skipped_module(self, module_name):
+ "Return True if module_name matches any skip pattern."
for pattern in self.skip:
if fnmatch.fnmatch(module_name, pattern):
return True
return False
def stop_here(self, frame):
+ "Return True if frame is below the starting frame in the stack."
# (CT) stopframe may now also be None, see dispatch_call.
# (CT) the former test for None is therefore removed from here.
if self.skip and \
@@ -145,6 +209,11 @@ class Bdb:
return False
def break_here(self, frame):
+ """Return True if there is an effective breakpoint for this line.
+
+ Check for line or function breakpoint and if in effect.
+ Delete temporary breakpoints if effective() says to.
+ """
filename = self.canonic(frame.f_code.co_filename)
if filename not in self.breaks:
return False
@@ -167,33 +236,43 @@ class Bdb:
return False
def do_clear(self, arg):
+ """Remove temporary breakpoint.
+
+ Must implement in derived classes or get NotImplementedError.
+ """
raise NotImplementedError("subclass of bdb must implement do_clear()")
def break_anywhere(self, frame):
+ """Return True if there is any breakpoint for frame's filename.
+ """
return self.canonic(frame.f_code.co_filename) in self.breaks
# Derived classes should override the user_* methods
# to gain control.
def user_call(self, frame, argument_list):
- """This method is called when there is the remote possibility
- that we ever need to stop in this function."""
+ """Called if we might stop in a function."""
pass
def user_line(self, frame):
- """This method is called when we stop or break at this line."""
+ """Called when when we stop or break at a line."""
pass
def user_return(self, frame, return_value):
- """This method is called when a return trap is set here."""
+ """Called when a return trap is set here."""
pass
def user_exception(self, frame, exc_info):
- """This method is called if an exception occurs,
- but only if we are to stop at or just below this level."""
+ """Called when we stop on an exception."""
pass
def _set_stopinfo(self, stopframe, returnframe, stoplineno=0):
+ """Set the attributes for stopping.
+
+ If stoplineno is greater than or equal to 0, then stop at line
+ greater than or equal to the stopline. If stoplineno is -1, then
+ don't stop at all.
+ """
self.stopframe = stopframe
self.returnframe = returnframe
self.quitting = False
@@ -205,8 +284,8 @@ class Bdb:
# to affect the stepping state.
def set_until(self, frame, lineno=None):
- """Stop when the line with the line no greater than the current one is
- reached or when returning from current frame"""
+ """Stop when the line with the lineno greater than the current one is
+ reached or when returning from current frame."""
# the name "until" is borrowed from gdb
if lineno is None:
lineno = frame.f_lineno + 1
@@ -236,7 +315,7 @@ class Bdb:
self._set_stopinfo(frame.f_back, frame)
def set_trace(self, frame=None):
- """Start debugging from `frame`.
+ """Start debugging from frame.
If frame is not specified, debugging starts from caller's frame.
"""
@@ -251,6 +330,10 @@ class Bdb:
sys.settrace(self.trace_dispatch)
def set_continue(self):
+ """Stop only at breakpoints or when finished.
+
+ If there are no breakpoints, set the system trace function to None.
+ """
# Don't stop except at breakpoints or when finished
self._set_stopinfo(self.botframe, None, -1)
if not self.breaks:
@@ -262,6 +345,10 @@ class Bdb:
frame = frame.f_back
def set_quit(self):
+ """Set quitting attribute to True.
+
+ Raises BdbQuit exception in the next call to a dispatch_*() method.
+ """
self.stopframe = self.botframe
self.returnframe = None
self.quitting = True
@@ -269,13 +356,18 @@ class Bdb:
# Derived classes and clients can call the following methods
# to manipulate breakpoints. These methods return an
- # error message is something went wrong, None if all is well.
+ # error message if something went wrong, None if all is well.
# Set_break prints out the breakpoint line and file:lineno.
# Call self.get_*break*() to see the breakpoints or better
# for bp in Breakpoint.bpbynumber: if bp: bp.bpprint().
def set_break(self, filename, lineno, temporary=False, cond=None,
funcname=None):
+ """Set a new breakpoint for filename:lineno.
+
+ If lineno doesn't exist for the filename, return an error message.
+ The filename should be in canonical form.
+ """
filename = self.canonic(filename)
import linecache # Import as late as possible
line = linecache.getline(filename, lineno)
@@ -285,14 +377,26 @@ class Bdb:
if lineno not in list:
list.append(lineno)
bp = Breakpoint(filename, lineno, temporary, cond, funcname)
+ return None
def _prune_breaks(self, filename, lineno):
+ """Prune breakpoints for filname:lineno.
+
+ A list of breakpoints is maintained in the Bdb instance and in
+ the Breakpoint class. If a breakpoint in the Bdb instance no
+ longer exists in the Breakpoint class, then it's removed from the
+ Bdb instance.
+ """
if (filename, lineno) not in Breakpoint.bplist:
self.breaks[filename].remove(lineno)
if not self.breaks[filename]:
del self.breaks[filename]
def clear_break(self, filename, lineno):
+ """Delete breakpoints for filename:lineno.
+
+ If no breakpoints were set, return an error message.
+ """
filename = self.canonic(filename)
if filename not in self.breaks:
return 'There are no breakpoints in %s' % filename
@@ -303,16 +407,26 @@ class Bdb:
for bp in Breakpoint.bplist[filename, lineno][:]:
bp.deleteMe()
self._prune_breaks(filename, lineno)
+ return None
def clear_bpbynumber(self, arg):
+ """Delete a breakpoint by its index in Breakpoint.bpbynumber.
+
+ If arg is invalid, return an error message.
+ """
try:
bp = self.get_bpbynumber(arg)
except ValueError as err:
return str(err)
bp.deleteMe()
self._prune_breaks(bp.file, bp.line)
+ return None
def clear_all_file_breaks(self, filename):
+ """Delete all breakpoints in filename.
+
+ If none were set, return an error message.
+ """
filename = self.canonic(filename)
if filename not in self.breaks:
return 'There are no breakpoints in %s' % filename
@@ -321,16 +435,27 @@ class Bdb:
for bp in blist:
bp.deleteMe()
del self.breaks[filename]
+ return None
def clear_all_breaks(self):
+ """Delete all existing breakpoints.
+
+ If none were set, return an error message.
+ """
if not self.breaks:
return 'There are no breakpoints'
for bp in Breakpoint.bpbynumber:
if bp:
bp.deleteMe()
self.breaks = {}
+ return None
def get_bpbynumber(self, arg):
+ """Return a breakpoint by its index in Breakpoint.bybpnumber.
+
+ For invalid arg values or if the breakpoint doesn't exist,
+ raise a ValueError.
+ """
if not arg:
raise ValueError('Breakpoint number expected')
try:
@@ -346,17 +471,26 @@ class Bdb:
return bp
def get_break(self, filename, lineno):
+ """Return True if there is a breakpoint for filename:lineno."""
filename = self.canonic(filename)
return filename in self.breaks and \
lineno in self.breaks[filename]
def get_breaks(self, filename, lineno):
+ """Return all breakpoints for filename:lineno.
+
+ If no breakpoints are set, return an empty list.
+ """
filename = self.canonic(filename)
return filename in self.breaks and \
lineno in self.breaks[filename] and \
Breakpoint.bplist[filename, lineno] or []
def get_file_breaks(self, filename):
+ """Return all lines with breakpoints for filename.
+
+ If no breakpoints are set, return an empty list.
+ """
filename = self.canonic(filename)
if filename in self.breaks:
return self.breaks[filename]
@@ -364,12 +498,18 @@ class Bdb:
return []
def get_all_breaks(self):
+ """Return all breakpoints that are set."""
return self.breaks
# Derived classes and clients can call the following method
# to get a data structure representing a stack trace.
def get_stack(self, f, t):
+ """Return a list of (frame, lineno) in a stack trace and a size.
+
+ List starts with original calling frame, if there is one.
+ Size may be number of frames above or below f.
+ """
stack = []
if t and t.tb_frame is f:
t = t.tb_next
@@ -388,6 +528,14 @@ class Bdb:
return stack, i
def format_stack_entry(self, frame_lineno, lprefix=': '):
+ """Return a string with information about a stack entry.
+
+ The stack entry frame_lineno is a (frame, lineno) tuple. The
+ return string contains the canonical filename, the function name
+ or '<lambda>', the input arguments, the return value, and the
+ line of code (if it exists).
+
+ """
import linecache, reprlib
frame, lineno = frame_lineno
filename = self.canonic(frame.f_code.co_filename)
@@ -418,6 +566,10 @@ class Bdb:
# Both can be given as a string, or a code object.
def run(self, cmd, globals=None, locals=None):
+ """Debug a statement executed via the exec() function.
+
+ globals defaults to __main__.dict; locals defaults to globals.
+ """
if globals is None:
import __main__
globals = __main__.__dict__
@@ -436,6 +588,10 @@ class Bdb:
sys.settrace(None)
def runeval(self, expr, globals=None, locals=None):
+ """Debug an expression executed via the eval() function.
+
+ globals defaults to __main__.dict; locals defaults to globals.
+ """
if globals is None:
import __main__
globals = __main__.__dict__
@@ -452,12 +608,17 @@ class Bdb:
sys.settrace(None)
def runctx(self, cmd, globals, locals):
+ """For backwards-compatibility. Defers to run()."""
# B/W compatibility
self.run(cmd, globals, locals)
# This method is more useful to debug a single function call.
def runcall(self, func, *args, **kwds):
+ """Debug a single function call.
+
+ Return the result of the function call.
+ """
self.reset()
sys.settrace(self.trace_dispatch)
res = None
@@ -472,6 +633,7 @@ class Bdb:
def set_trace():
+ """Start debugging with a Bdb instance from the caller's frame."""
Bdb().set_trace()
@@ -482,11 +644,15 @@ class Breakpoint:
(re)-enabling, and conditionals.
Breakpoints are indexed by number through bpbynumber and by
- the file,line tuple using bplist. The former points to a
+ the (file, line) tuple using bplist. The former points to a
single instance of class Breakpoint. The latter points to a
list of such instances since there may be more than one
breakpoint per line.
+ When creating a breakpoint, its associated filename should be
+ in canonical form. If funcname is defined, a breakpoint hit will be
+ counted when the first line of that function is executed. A
+ conditional breakpoint always counts a hit.
"""
# XXX Keeping state in the class is a mistake -- this means
@@ -519,6 +685,12 @@ class Breakpoint:
self.bplist[file, line] = [self]
def deleteMe(self):
+ """Delete the breakpoint from the list associated to a file:line.
+
+ If it is the last breakpoint in that position, it also deletes
+ the entry for the file:line.
+ """
+
index = (self.file, self.line)
self.bpbynumber[self.number] = None # No longer in list
self.bplist[index].remove(self)
@@ -527,17 +699,31 @@ class Breakpoint:
del self.bplist[index]
def enable(self):
+ """Mark the breakpoint as enabled."""
self.enabled = True
def disable(self):
+ """Mark the breakpoint as disabled."""
self.enabled = False
def bpprint(self, out=None):
+ """Print the output of bpformat().
+
+ The optional out argument directs where the output is sent
+ and defaults to standard output.
+ """
if out is None:
out = sys.stdout
print(self.bpformat(), file=out)
def bpformat(self):
+ """Return a string with information about the breakpoint.
+
+ The information includes the breakpoint number, temporary
+ status, file:line position, break condition, number of times to
+ ignore, and number of times hit.
+
+ """
if self.temporary:
disp = 'del '
else:
@@ -561,12 +747,20 @@ class Breakpoint:
return ret
def __str__(self):
+ "Return a condensed description of the breakpoint."
return 'breakpoint %s at %s:%s' % (self.number, self.file, self.line)
# -----------end of Breakpoint class----------
+
def checkfuncname(b, frame):
- """Check whether we should break here because of `b.funcname`."""
+ """Return True if break should happen here.
+
+ Whether a break should happen depends on the way that b (the breakpoint)
+ was set. If it was set via line number, check if b.line is the same as
+ the one in the frame. If it was set via function name, check if this is
+ the right function and if it is on the first executable line.
+ """
if not b.funcname:
# Breakpoint was set via line number.
if b.line != frame.f_lineno:
@@ -576,7 +770,6 @@ def checkfuncname(b, frame):
return True
# Breakpoint set via function name.
-
if frame.f_code.co_name != b.funcname:
# It's not a function call, but rather execution of def statement.
return False
@@ -586,20 +779,21 @@ def checkfuncname(b, frame):
# The function is entered for the 1st time.
b.func_first_executable_line = frame.f_lineno
- if b.func_first_executable_line != frame.f_lineno:
+ if b.func_first_executable_line != frame.f_lineno:
# But we are not at the first line number: don't break.
return False
return True
+
# Determines if there is an effective (active) breakpoint at this
# line of code. Returns breakpoint number or 0 if none
def effective(file, line, frame):
"""Determine which breakpoint for this file:line is to be acted upon.
- Called only if we know there is a bpt at this
- location. Returns breakpoint that was triggered and a flag
- that indicates if it is ok to delete a temporary bp.
-
+ Called only if we know there is a breakpoint at this location. Return
+ the breakpoint that was triggered and a boolean that indicates if it is
+ ok to delete a temporary breakpoint. Return (None, None) if there is no
+ matching breakpoint.
"""
possibles = Breakpoint.bplist[file, line]
for b in possibles: