The Python code module provides the core classes for building read-eval-print loops, commonly called REPLs. With InteractiveInterpreter and InteractiveConsole, an application can embed a Python prompt, maintain a namespace, capture errors, and decide how incomplete blocks are buffered.
This is useful in teaching tools, debuggers, lightweight notebooks, administrative consoles, and local diagnostic environments. However, it executes real Python code. An embedded REPL is not a sandbox for untrusted users: imports, files, networks, processes, introspection, and objects already present in the namespace may all be reachable.
InteractiveInterpreter versus InteractiveConsole
InteractiveInterpreter handles compilation, namespace state, and execution. It does not provide prompts or multiline buffering. InteractiveConsole builds on it and adds behavior similar to the standard interpreter, including primary and continuation prompts.
from code import InteractiveConsole
console = InteractiveConsole()
console.interact(
banner='Diagnostic console',
exitmsg='Console closed',
)This opens a REPL inside the current process. Every command has the same operating-system permissions as the application.
Provide a custom namespace
The locals argument accepts a mapping used as the execution namespace. It lets you expose useful objects and keep variables between commands.
from code import InteractiveConsole
namespace = {
'status': lambda: {'queue': 4, 'health': 'ok'},
'version': '2.1.0',
}
console = InteractiveConsole(locals=namespace)A reduced mapping improves ergonomics but does not create a security boundary. Python introspection and reachable objects can provide indirect access to sensitive capabilities.
Compile and execute with runsource()
runsource() compiles and executes a source string. Its return value indicates whether more input is required: True means incomplete input; False means it ran or was rejected.
from code import InteractiveInterpreter
interpreter = InteractiveInterpreter()
more = interpreter.runsource('for i in range(3):')
print(more) # TrueThis protocol lets a custom interface switch between a primary prompt and a continuation prompt.
Buffer blocks with push()
InteractiveConsole.push() appends a line to an internal buffer and tries to compile the complete content.
from code import InteractiveConsole
console = InteractiveConsole()
print(console.push('def double(x):'))
print(console.push(' return x * 2'))
print(console.push(''))
print(console.push('double(5)'))When a block completes or becomes invalid, the buffer is reset. resetbuffer() discards pending input after a cancellation or interface reset.
Detect incomplete Python input
compile_command() tries to make the same complete-versus-incomplete decision as the real interpreter. It returns a code object for valid complete input, None for incomplete input, and raises SyntaxError for complete invalid input.
from code import compile_command
result = compile_command('if active:', symbol='single')
assert result is NoneThe guide to Python codeop covers persistent compiler flags and future statements in greater detail.
Customize input with raw_input()
Subclasses can override raw_input(). This makes it possible to read from a graphical editor, a local socket, a browser interface, or an internal queue.
class QueueConsole(InteractiveConsole):
def __init__(self, queue, **kwargs):
super().__init__(**kwargs)
self.queue = queue
def raw_input(self, prompt=''):
display_prompt(prompt)
return self.queue.get()Do not expose this directly to the public internet. A remote console requires strong authentication, restricted networking, a dedicated unprivileged process, resource limits, timeouts, and complete auditing.
Capture errors by overriding write()
Syntax messages and tracebacks normally go to sys.stderr. Override write() to collect them or send them to another interface.
class CapturingConsole(InteractiveConsole):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.errors = []
def write(self, data):
self.errors.append(data)Avoid returning complete tracebacks to external users because they may reveal paths, module names, configuration, and data. Keep detailed diagnostics in protected logs.
Syntax errors and runtime tracebacks
showsyntaxerror() formats compile-time problems, while showtraceback() handles runtime exceptions and removes the interpreter’s own frame. Chained exceptions have been displayed fully since Python 3.5.
You may override these methods for structured JSON or localized output, but preserve the original exception internally for debugging.
Execute compiled objects with runcode()
runcode() runs a compiled code object. Ordinary exceptions are displayed, but SystemExit may propagate. KeyboardInterrupt can also arise outside the expected point.
compiled = compile('answer = 6 * 7', '<console>', 'exec')
interpreter = InteractiveInterpreter()
interpreter.runcode(compiled)
print(interpreter.locals['answer'])The embedding application needs a clear policy for process exit, cancellation, and cleanup.
local_exit in Python 3.13 and newer
InteractiveConsole and interact() accept local_exit=True. In this mode, exit() and quit() return from the console instead of raising SystemExit through the whole host application.
console = InteractiveConsole(local_exit=True)
console.interact()This improves embedding, but code can still call os._exit(), alter threads, close resources, or damage the process in other ways.
Use code.interact() for convenience
code.interact() creates a temporary console. It accepts a banner, read function, namespace, exit message, and local-exit setting.
import code
state = {'order_id': 123, 'mode': 'debug'}
code.interact(
banner='Local support session',
local=state,
exitmsg='',
local_exit=True,
)This can be placed behind a development flag. Make sure the flag is disabled in production deployments.
A REPL for local debugging
You can start a trusted console at a specific diagnostic point and expose a carefully chosen object.
def diagnose(obj):
code.interact(
local={'object': obj, 'summary': obj.summary},
local_exit=True,
)Do not include credentials, tokens, or objects with destructive methods. Even a local terminal may be shared, recorded, or accessed through an administrative account.
Namespace persistence and memory
Variables, functions, and classes remain in the locals mapping while the interpreter instance lives. This supports natural exploration but can retain large objects and grow memory usage.
Offer a reset command, remove temporary names, or recreate the console periodically. The article on Python weakref explains object retention, although lifecycle policy should remain explicit.
Pickle limitations
Functions and classes defined in an interpreter belong to the provided namespace. They are conventionally pickleable only when that namespace corresponds to an existing importable module. Do not expect arbitrary interactive sessions to serialize cleanly.
Persist data through defined formats and reconstruct executable behavior from version-controlled source. Never unpickle untrusted input.
Real isolation requires another process
Removing __builtins__ or blocking a handful of names does not create a Python sandbox. Introspection and reachable types make such filters unreliable.
For reduced impact, run the REPL in a disposable process or container with an unprivileged user, limited filesystem, no network, CPU and memory limits, and a strict timeout. Even then, treat it as code execution rather than data entry.
Test a custom console
Test return values from push(), namespace changes, captured output, syntax errors, runtime exceptions, incomplete blocks, EOF, and local exit.
def test_console():
console = CapturingConsole(locals={})
assert console.push('x = 10') is False
assert console.locals['x'] == 10
assert console.push('for i in range(2):') is True
console.resetbuffer()Inject fake services and never connect unit tests to production resources.
Common mistakes
- Treating a reduced namespace as a sandbox.
- Exposing the REPL on a public port.
- Leaving debug consoles enabled in production.
- Returning sensitive tracebacks to clients.
- Ignoring
SystemExitandKeyboardInterrupt. - Retaining large objects indefinitely.
- Trying to persist session functions with pickle.
Best practices
- Use the module only for trusted operators.
- Prefer disposable processes for isolation.
- Control namespace and session lifetime.
- Capture output through
write(). - Limit CPU, memory, network, and wall time.
- Disable the feature by default in production.
- Audit access without logging secrets.
Conclusion
The Python code module makes custom REPLs practical by providing persistent namespaces, block buffering, prompts, error display, and local exit behavior. It is a powerful base for development and diagnostic tools.
Its security meaning must be explicit: the input is executable code. For untrusted users, rely on operating-system isolation, not namespace filters. Consult the official code module documentation and the codeop documentation.







