Muchly changed and improved pprint.py:

	- handles recursive data structures
	- formatting based on a PrettyPrinter object
	- allows a maximum nesting depth to be specified
	- provides safe repr()-like function which does not pretty-print
diff --git a/Lib/pprint.py b/Lib/pprint.py
index a39dd13..850b0f7 100644
--- a/Lib/pprint.py
+++ b/Lib/pprint.py
@@ -1,7 +1,7 @@
 #  pprint.py
 #
 #  Author:	Fred L. Drake, Jr.
-#		fdrake@cnri.reston.va.us, fdrake@intr.net
+#		fdrake@cnri.reston.va.us, fdrake@acm.org
 #
 #  This is a simple little module I wrote to make life easier.  I didn't
 #  see anything quite like it in the library, though I may have overlooked
@@ -14,6 +14,13 @@
 
 Very simple, but useful, especially in debugging data structures.
 
+Classes
+-------
+
+PrettyPrinter()
+    Handle pretty-printing operations onto a stream using a configured
+    set of formatting parameters.
+
 Functions
 ---------
 
@@ -21,123 +28,173 @@
     Format a Python object into a pretty-printed representation.
 
 pprint()
-    Pretty-print a list, tuple or dictionary.
+    Pretty-print a Python object to a stream [default is sys.sydout].
 
-
-
-Constants
----------
-
-INDENT_PER_LEVEL
-    Amount of indentation to use for each new recursive level.  The
-    default is 1.  This must be a non-negative integer, and may be set
-    by the caller before calling pprint().
-
-MAX_WIDTH
-    Maximum width of the display.  This is only used if the
-    representation *can* be kept less than MAX_WIDTH characters wide.
-    May be set by the user before calling pprint() if needed.
+saferepr()
+    Generate a 'standard' repr()-like value, but protect against recursive
+    data structures.
 
 """
 
-INDENT_PER_LEVEL = 1
-
-MAX_WIDTH = 80
-
 from types import DictType, ListType, TupleType
 
-
-def pformat(seq):
-    """Format a Python object into a pretty-printed representation.
-
-    The representation is returned with no trailing newline.
-
-    """
-    import StringIO
-    sio = StringIO.StringIO()
-    pprint(seq, stream=sio)
-    str = sio.getvalue()
-    if str and str[-1] == '\n':
-	str = str[:-1]
-    return str
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
 
 
-def pprint(seq, stream=None, indent=0, allowance=0):
-    """Pretty-print a list, tuple, or dictionary.
+def pprint(object, stream=None):
+    """Pretty-print a Python object to a stream [default is sys.sydout]."""
+    printer = PrettyPrinter(stream=stream)
+    printer.pprint(object)
 
-    seq
-	List, tuple, or dictionary object to be pretty-printed.  Other
-	object types are permitted by are not specially interpreted.
 
-    stream
-	Output stream.  If not provided, `sys.stdout' is used.  This
-	parameter must support the `write()' method with a single
-	parameter, which will always be a string.  It may be a
-	`StringIO.StringIO' object if the result is needed as a
-	string.
+def pformat(object):
+    """Format a Python object into a pretty-printed representation."""
+    return PrettyPrinter().pformat(object)
 
-    Indentation is done according to `INDENT_PER_LEVEL', which may be
-    set to any non-negative integer before calling this function.  The
-    output written on the stream is a perfectly valid representation
-    of the Python object passed in, with indentation to assist
-    human-readable interpretation.  The output can be used as input
-    without error, given readable representations of all elements are
-    available via `repr()'.  Output is restricted to `MAX_WIDTH'
-    columns where possible.
 
-    """
-    if stream is None:
-	import sys
-	stream = sys.stdout
+def saferepr(object):
+    """Version of repr() which can handle recursive data structures."""
+    return _safe_repr(object, {})
 
-    rep = `seq`
-    typ = type(seq)
-    sepLines = len(rep) > (MAX_WIDTH - 1 - indent - allowance)
 
-    if sepLines and (typ is ListType or typ is TupleType):
-	#  Pretty-print the sequence.
-	stream.write(((typ is ListType) and '[') or '(')
+class PrettyPrinter:
+    def __init__(self, indent=1, width=80, depth=None, stream=None):
+	"""Handle pretty printing operations onto a stream using a set of
+	configured parameters.
 
-	length = len(seq)
-	if length:
-	    indent = indent + INDENT_PER_LEVEL
-	    pprint(seq[0], stream, indent, allowance + 1)
+	indent
+	    Number of spaces to indent for each level of nesting.
 
-	    if len(seq) > 1:
-		for ent in seq[1:]:
-		    stream.write(',\n' + ' '*indent)
-		    pprint(ent, stream, indent, allowance + 1)
+	width
+	    Attempted maximum number of columns in the output.
 
-	    indent = indent - INDENT_PER_LEVEL
+	depth
+	    The maximum depth to print out nested structures.
 
-	stream.write(((typ is ListType) and ']') or ')')
+	stream
+	    The desired output stream.  If omitted (or false), the standard
+	    output stream available at construction will be used.
 
-    elif typ is DictType and sepLines:
-	stream.write('{')
+	"""
+	assert (not depth) or depth > 0, "depth may not be negative"
+	assert int(indent) or 1
+	assert int(width) or 1
+	self.__depth = depth
+	self.__indent_per_level = indent
+	self.__width = width
+	if stream:
+	    self.__stream = stream
+	else:
+	    import sys
+	    self.__stream = sys.stdout
 
-	length = len(seq)
-	if length:
-	    indent = indent + INDENT_PER_LEVEL
-	    items  = seq.items()
-	    items.sort()
-	    key, ent = items[0]
-	    rep = `key` + ': '
+    def pprint(self, object):
+	self.__stream.write(self.pformat(object) + "\n")
+
+    def pformat(self, object):
+	sio = StringIO()
+	self.__format(object, sio, 0, 0, {}, 0)
+	return sio.getvalue()
+
+    def __format(self, object, stream, indent, allowance, context, level):
+	level = level + 1
+	if context.has_key(id(object)):
+	    object = _Recursion(object)
+	rep = self__repr(object, context, level - 1)
+	objid = id(object)
+	context[objid] = 1
+	typ = type(object)
+	sepLines = len(rep) > (self.__width - 1 - indent - allowance)
+
+	if sepLines and typ in (ListType, TupleType):
+	    #  Pretty-print the sequence.
+	    stream.write((typ is ListType) and '[' or '(')
+	    length = len(object)
+	    if length:
+		indent = indent + self.__indent_per_level
+		pprint(object[0], stream, indent, allowance + 1)
+		if len(object) > 1:
+		    for ent in object[1:]:
+			stream.write(',\n' + ' '*indent)
+			self.__format(ent, stream, indent,
+				      allowance + 1, context, level)
+		indent = indent - self.__indent_per_level
+	    stream.write(((typ is ListType) and ']') or ')')
+
+	elif sepLines and typ is DictType:
+	    stream.write('{')
+	    length = len(object)
+	    if length:
+		indent = indent + self.__indent_per_level
+		items  = object.items()
+		items.sort()
+		key, ent = items[0]
+		rep = self.__repr(key, context, level) + ': '
+		stream.write(rep)
+		self.__format(ent, stream, indent + len(rep),
+			      allowance + 1, context, level)
+		if len(items) > 1:
+		    for key, ent in items[1:]:
+			rep = self.__repr(key, context, level) + ': '
+			stream.write(',\n' + ' '*indent + rep)
+			self.__format(ent, stream, indent + len(rep),
+				      allowance + 1, context, level)
+		indent = indent - self.__indent_per_level
+	    stream.write('}')
+
+	else:
 	    stream.write(rep)
-	    pprint(ent, stream, indent + len(rep), allowance + 1)
+	    del context[objid]
 
-	    if len(items) > 1:
-		for key, ent in items[1:]:
-		    rep = `key` + ': '
-		    stream.write(',\n' + ' '*indent + rep)
-		    pprint(ent, stream, indent + len(rep), allowance + 1)
+    def __repr(self, object, context, level):
+	return _safe_repr(object, context, self.__depth, level)
 
-	    indent = indent - INDENT_PER_LEVEL
 
-	stream.write('}')
-
+def _safe_repr(object, context=None, maxlevels=None, level=0):
+    level = level + 1
+    typ = type(object)
+    if not (typ in (DictType, ListType, TupleType) and object):
+	return `object`
+    if context is None:
+	context = {}
     else:
-	stream.write(rep)
+	if context.has_key(id(object)):
+	    return `_Recursion(object)`
+    objid = id(object)
+    context[objid] = 1
+    if typ is DictType:
+	if maxlevels and level >= maxlevels:
+	    s = "{...}"
+	else:
+	    items = object.items()
+	    k, v = items[0]
+	    s = "{%s: %s" % (_safe_repr(k, context), _safe_repr(v, context))
+	    for k, v in items[1:]:
+		s = "%s, %s: %s" \
+		    % (s, _safe_repr(k, context), _safe_repr(v, context))
+	    s = s + "}"
+    else:
+	s, term = (typ is ListType) and ('[', ']') or ('(', ')')
+	if maxlevels and level >= maxlevels:
+	    s = s + "..."
+	else:
+	    s = s + _safe_repr(object[0], context)
+	    for ent in object[1:]:
+		s = "%s, %s" % (s, _safe_repr(ent, context))
+	s = s + term
+    del context[objid]
+    return s
 
-    #  Terminate the 'print' if we're not a recursive invocation.
-    if not indent:
-	stream.write('\n')
+
+class _Recursion:
+    # represent a recursive relationship; really only used for the __repr__()
+    # method...
+    def __init__(self, object):
+	self.__repr = "<Recursion on %s with id=%s>" \
+		      % (type(object).__name__, id(object))
+
+    def __repr__(self):
+	return self.__repr