C.W.K.
Stream
Lesson 03 of 07 · published

The REPL Deep Dive — Beyond `>>> hello`

~18 min · repl, ipython, history, completion, underscore, exec-eval

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

The REPL is your debugger that's always on

Most languages have an interactive shell of some sort, but in Python it's a first-class part of how the language is used day-to-day. Not a teaching toy. The REPL is where you sketch ideas before writing the file, where you debug live objects without restarting your program, where you confirm "does this method actually return what I think?" before committing to a refactor.

The version that ships with Python 3.13 finally got proper multi-line editing, syntax highlighting, and a much improved help/exit experience. If you're on 3.13+, you've already got it. If you're on 3.12 or earlier, this lesson covers what to install (ipython, bpython) to get the same quality of life.

Built-in features you probably missed

Even the plain python REPL has features most people never discover:

  • Up/Down arrow walks through your input history. Hit up — your last expression comes back, editable.
  • Tab completes attribute names, method names, even local variables. Type str. and hit tab — Python lists every method on str.
  • The _ (underscore) variable always holds the result of the last expression evaluated at the prompt. So you can type 2 + 2, then _ * 10 on the next line, and get 40. Chains like this are the REPL's superpower.
  • help(thing) opens the docstring for any object — function, class, module. Press q to exit help.
  • dir(thing) lists every attribute and method on an object. Less polished than help, but exhaustive.
Tip: help() with no argument enters Python's full interactive help system — the same docs as docs.python.org, browsable from the prompt. Useful when offline or when you don't want to context-switch to a browser.

Multi-line editing in 3.13

Pre-3.13 REPLs had a famous wart — once you started typing a multi-line block (a function definition, a for-loop), you couldn't go back and edit earlier lines. Mistype something on line 1 of a five-line function? Cancel and re-type from scratch.

Python 3.13's new REPL fixes this. You can up-arrow into earlier lines of an in-progress block, edit them, and execute the whole thing. F1 brings up the help system inline. F2 brings up history search. F3 toggles paste mode (so you can paste indented code without the auto-indenter fighting you).

python -i script.py — script then drop into REPL

One of the highest-leverage flags in the entire python command. python -i myscript.py runs the script normally — and when it finishes (or crashes), drops you into a REPL where every variable, function, and class the script defined is still alive in your namespace.

This is how you debug a script without setting up pdb or any IDE: when something crashes, the traceback prints, and then >>> appears with full access to whatever state existed at crash time. Inspect, poke, fix, re-run.

Self-reference: When something in cwkPippa's adapter layer behaves strangely under Claude streaming, the move is often python -i on a small repro script. The script crashes mid-stream; the REPL drops me in with the half-built response object still alive; I poke at it for two minutes and the bug is obvious. No debugger setup, no breakpoints — just a REPL with the corpse still warm.

ipython and bpython — REPLs with superpowers

Even with 3.13's improvements, two third-party REPLs are worth knowing:

  • ipython — the data science REPL. Magic commands (%timeit, %run, %load), better tracebacks, automatic pretty-printing, and integration with Jupyter notebooks. pip install ipython then ipython launches it. Once you've used %timeit for benchmarking, the plain REPL feels primitive.
  • bpython — lightweight, lovely UI, inline auto-suggestions as you type. Less feature-loaded than ipython, but more pleasant for casual exploration. pip install bpython.

You don't need both. Pick one (most devs end up on ipython) and add it to your default install kit.

exec() and eval() — when, why, and the security note

Two built-in functions occasionally tempt beginners — eval(string) evaluates a Python expression, exec(string) executes Python statements. Both take a string and run it as code.

The temptation: "I want the user to type a math expression and get the answer" → eval(input()). The reality: that one line lets the user type __import__('os').system('rm -rf ~') and your machine catches fire.

Warning: Never pass untrusted input to eval or exec. Ever. If you find yourself reaching for them to parse user input, what you actually want is ast.literal_eval (safe — only evaluates literals like numbers, strings, lists), or a real parser like pyparsing, or just json.loads if the input is JSON. eval on untrusted input is an arbitrary-code-execution vulnerability with extra steps.

That said, both functions have legitimate uses — runtime code generation, REPL implementations, dynamic class building. The Standard Library track will revisit them in context.

Bottom line

The REPL is not a beginner-only tool. Many seasoned Python developers spend more time in ipython than in their editor for exploratory work. Master the four basics (history, tab completion, _, help/dir), install ipython, and treat the REPL as your live debugger.

Pythonic Way: When you're stuck on "what does this object actually do?" — don't read the source first, don't search Stack Overflow first. Open the REPL, import the thing, call dir() and help() on it. The Pythonic answer to "how does this work?" is often "ask the object yourself."

Code

Tab completion + history + the underscore trick·python
>>> import string
>>> string.<TAB>           # press Tab — Python shows every name in the string module
string.ascii_letters     string.ascii_lowercase   string.ascii_uppercase
string.capwords(         string.digits            string.hexdigits
...

>>> 2 + 2
4
>>> _ * 10                 # _ holds the last result
40
>>> _ + 5                  # and updates each time
45

>>> # Up-arrow brings back the previous line, editable.
>>> # Try it: type something, press Up, edit, press Enter.
help() and dir() — asking the object what it does·python
>>> dir("hello")
['__add__', '__class__', ..., 'capitalize', 'casefold', 'center',
 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format',
 'format_map', 'index', 'isalnum', ..., 'upper', 'zfill']

>>> help(str.upper)
Help on method_descriptor:

upper(self, /)
    Return a copy of the string converted to uppercase.

>>> # help() with no arg → full interactive docs.
>>> # Press q to exit any help screen.
python -i script.py — REPL on top of a finished script·bash
# my_buggy_script.py
x = [1, 2, 3]
y = [n * 2 for n in x]
z = y[10]    # IndexError: list index out of range

$ python -i my_buggy_script.py
Traceback (most recent call last):
  File "my_buggy_script.py", line 3, in <module>
    z = y[10]
        ~^^^^
IndexError: list index out of range
>>> x          # all script-defined names are alive
[1, 2, 3]
>>> y
[2, 4, 6]
>>> len(y)     # ah — only 3 items, indexing 10 was nonsense
3
>>> # Inspect, fix the script, re-run with python -i again.
ipython tour — what makes it different·python
$ pip install ipython
$ ipython

In [1]: %timeit sum(range(1000))     # magic command — micro-benchmark
8.42 µs ± 50.1 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

In [2]: %run myscript.py             # execute a file in the current namespace

In [3]: import requests
In [4]: r = requests.get("https://api.github.com")
In [5]: r.<TAB>                       # ipython's tab completion is richer

In [6]: r?                            # one-? shows docstring
In [7]: r??                           # two-? shows source code

In [8]: history                       # full session history, line-numbered
eval/exec — the security trap (don't do this with user input)·python
# DANGEROUS — never do this with user input.
user_expr = input("Math expression: ")
result = eval(user_expr)              # arbitrary code execution
# user types: __import__('os').system('rm -rf ~')
# game over.

# SAFE alternative for math/literal expressions:
import ast
user_expr = input("Math expression: ")
result = ast.literal_eval(user_expr)  # only literals — safe

# SAFE alternative for JSON input:
import json
data = json.loads(input("JSON: "))    # parser, not evaluator

External links

Exercise

Install ipython (pip install ipython) and run it. Re-do lesson 1's three code blocks inside ipython. Notice the differences from the plain REPL — the prompt format, the auto-pretty-printing, the colored output. Then try ? (one question mark) on print and ?? on len — and notice how each tells you something different.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.