Subsume the interact() function in a class.  This should make it
possible to use this in PythonWin, and to replace Fredrik Lundh's
PythonInterpreter class.  Fredrik is credited with the class' API.
diff --git a/Lib/code.py b/Lib/code.py
index d0ff4bf..f28371f 100644
--- a/Lib/code.py
+++ b/Lib/code.py
@@ -1,5 +1,9 @@
 """Utilities dealing with code objects."""
 
+import sys
+import string
+import traceback
+
 def compile_command(source, filename="<input>", symbol="single"):
     r"""Compile a command and determine whether it is incomplete.
 
@@ -60,51 +64,202 @@
         raise SyntaxError, err1
 
 
-def interact(banner=None, readfunc=raw_input, local=None):
-    # Due to Jeff Epler, with changes by Guido:
-    """Closely emulate the interactive Python console."""
-    try: import readline # Enable GNU readline if available
-    except: pass
-    local = local or {}
-    import sys, string, traceback
-    sys.ps1 = '>>> '
-    sys.ps2 = '... '
-    if banner:
-        print banner
-    else:
-        print "Python Interactive Console", sys.version
-        print sys.copyright
-    buf = []
-    while 1:
-        if buf: prompt = sys.ps2
-        else: prompt = sys.ps1
-        try: line = readfunc(prompt)
-        except KeyboardInterrupt:
-            print "\nKeyboardInterrupt"
-            buf = []
-            continue
-        except EOFError: break
-        buf.append(line)
-        try: x = compile_command(string.join(buf, "\n"))
-        except SyntaxError:
-            traceback.print_exc(0)
-            buf = []
-            continue
-        if x == None: continue
+class InteractiveConsole:
+    """Closely emulate the behavior of the interactive Python interpreter.
+
+    After code by Jeff Epler and Fredrik Lundh.
+    """
+
+    def __init__(self, filename="<console>", locals=None):
+        """Constructor.
+
+        The optional filename argument specifies the (file)name of the
+        input stream; it will show up in tracebacks.  It defaults to
+        '<console>'.
+
+        """
+        self.filename = filename
+        if locals is None:
+            locals = {}
+        self.locals = locals
+        self.resetbuffer()
+
+    def resetbuffer(self):
+        """Reset the input buffer (but not the variables!)."""
+        self.buffer = []
+
+    def interact(self, banner=None):
+        """Closely emulate the interactive Python console."""
+        try:
+            sys.ps1
+        except AttributeError:
+            sys.ps1 = ">>> "
+        try:
+            sys.ps2
+        except AttributeError:
+            sys.ps2 = "... "
+        if banner is None:
+            self.write("Python %s on %s\n%s\n(%s)\n" %
+                       (sys.version, sys.platform, sys.copyright,
+                        self.__class__.__name__))
         else:
-            try: exec x in local
+            self.write("%s\n" % str(banner))
+        more = 0
+        while 1:
+            try:
+                if more:
+                    prompt = sys.ps2
+                else:
+                    prompt = sys.ps1
+                try:
+                    line = self.raw_input(prompt)
+                except EOFError:
+                    self.write("\n")
+                    break
+                else:
+                    more = self.push(line)
+            except KeyboardInterrupt:
+                self.write("\nKeyboardInterrupt\n")
+                self.resetbuffer()
+                more = 0
+
+    def push(self, line):
+        """Push a line to the interpreter.
+
+        The line should not have a trailing newline.
+
+        One of three things will happen:
+
+        1) The input is incorrect; compile_command() raised
+        SyntaxError.  A syntax traceback will be printed.
+
+        2) The input is incomplete, and more input is required;
+        compile_command() returned None.
+
+        3) The input is complete; compile_command() returned a code
+        object.  The code is executed.  When an exception occurs, a
+        traceback is printed.  All exceptions are caught except
+        SystemExit, which is reraised.
+
+        The return value is 1 in case 2, 0 in the other cases.  (The
+        return value can be used to decide whether to use sys.ps1 or
+        sys.ps2 to prompt the next line.)
+
+        A note about KeyboardInterrupt: this exception may occur
+        elsewhere in this code, and will not always be caught.  The
+        caller should be prepared to deal with it.
+
+        """
+        self.buffer.append(line)
+
+        try:
+            x = compile_command(string.join(self.buffer, "\n"),
+                                filename=self.filename)
+        except SyntaxError:
+            # Case 1
+            self.showsyntaxerror()
+            self.resetbuffer()
+            return 0
+
+        if x is None:
+            # Case 2
+            return 1
+
+        # Case 3
+        try:
+            exec x in self.locals
+        except SystemExit:
+            raise
+        except:
+            self.showtraceback()
+        self.resetbuffer()
+        return 0
+
+    def showsyntaxerror(self):
+        """Display the syntax error that just occurred.
+
+        This doesn't display a stack trace because there isn't one.
+
+        The output is written by self.write(), below.
+
+        """
+        type, value = sys.exc_info()[:2]
+        # Work hard to stuff the correct filename in the exception
+        try:
+            msg, (filename, lineno, offset, line) = value
+        except:
+            pass
+        else:
+            try:
+                value = SyntaxError(msg, (self.filename, lineno, offset, line))
             except:
-                exc_type, exc_value, exc_traceback = \
-                        sys.exc_type, sys.exc_value, \
-                        sys.exc_traceback
-                l = len(traceback.extract_tb(sys.exc_traceback))
-                try: 1/0
-                except:
-                    m = len(traceback.extract_tb(
-                            sys.exc_traceback))
-                traceback.print_exception(exc_type,
-                        exc_value, exc_traceback, l-m)
-            buf = []
+                value = msg, (self.filename, lineno, offset, line)
+        list = traceback.format_exception_only(type, value)
+        map(self.write, list)
+
+    def showtraceback(self):
+        """Display the exception that just occurred.
+
+        We remove the first stack item because it is our own code.
+
+        The output is written by self.write(), below.
+
+        """
+        try:
+            type, value, tb = sys.exc_info()
+            tblist = traceback.extract_tb(tb)
+            del tblist[0]
+            list = traceback.format_list(tblist)
+            list[len(list):] = traceback.format_exception_only(type, value)
+        finally:
+            tblist = tb = None
+        map(self.write, list)
+
+    def write(self, data):
+        """Write a string.
+
+        The base implementation writes to sys.stderr; a subclass may
+        replace this with a different implementation.
+
+        """
+        sys.stderr.write(data)
+
+    def raw_input(self, prompt=""):
+        """Write a prompt and read a line.
+
+        The returned line does not include the trailing newline.
+        When the user enters the EOF key sequence, EOFError is raised.
+
+        The base implementation uses the built-in function
+        raw_input(); a subclass may replace this with a different
+        implementation.
+
+        """
+        return raw_input(prompt)
+
+
+def interact(banner=None, readfunc=None, locals=None):
+    """Closely emulate the interactive Python interpreter.
+
+    This is a backwards compatible interface to the InteractiveConsole
+    class.  It attempts to import the readline module to enable GNU
+    readline if it is available.
+
+    Arguments (all optional, all default to None):
+
+    banner -- passed to InteractiveConsole.interact()
+    readfunc -- if not None, replaces InteractiveConsole.raw_input()
+    locals -- passed to InteractiveConsole.__init__()
+
+    """
+    try:
+        import readline
+    except:
+        pass
+    console = InteractiveConsole(locals=locals)
+    if readfunc is not None:
+        console.raw_input = readfunc
+    console.interact(banner)
                 
 if __name__ == '__main__':
     interact()