ipython
IPython is the interactive Python shell that replaced the plain >>> prompt for most people who work in a terminal. It adds tab completion backed by jedi, object introspection with a trailing question mark, shell commands inline via !, magic commands like %timeit and %run, output history you can reach with _ and _3, and colored tracebacks that show surrounding source. It is also the engine under Jupyter: ipykernel wraps IPython's InteractiveShell to execute notebook cells. The pip package gives you a terminal program plus a library you can embed in your own process; the browser notebook lives in separate Jupyter packages.
Still the default interactive Python shell, and the thing your notebook stack is built on, so most environments end up with it anyway. Install it in dev and test environments freely; think twice before it rides along into a slim production image just for a nicer prompt.
Use it if
- You do exploratory work in a terminal and want tab completion, obj? introspection, and tracebacks that print surrounding source instead of bare line numbers
- You are developing a library and want %load_ext autoreload plus %autoreload 2 so edits on disk take effect without restarting the session
- You want post-mortem debugging on demand: %debug drops you into pdb at the frame that raised, and %pdb on makes that automatic
- You are building a notebook or kernel stack, since ipykernel and therefore Jupyter run on top of IPython's InteractiveShell
- You wanted notebooks: this package is the terminal shell and kernel core only, and the browser UI needs jupyterlab or notebook installed separately
- You are trimming a production container: IPython pulls jedi, prompt_toolkit (pinned below 3.1.0), pygments, traitlets, stack_data, psutil, and pexpect, and that prompt_toolkit ceiling regularly collides with other CLI tools that want a newer one
- You are on Python 3.10 or older: the 9.x line follows SPEC-0 and requires Python 3.11 or newer, so older interpreters are stuck on IPython 8
- You want a small embeddable REPL rather than a whole environment: ptpython and bpython start faster and carry far fewer dependencies
- You plan to paste session code straight into .py files: lines with ! or % are IPython syntax, not Python, and they raise SyntaxError under a plain interpreter
Setup reality
pip install ipython works in seconds with no compile step, but it is not one package: expect jedi, prompt_toolkit, pygments, traitlets, stack_data, matplotlib-inline, ipython-pygments-lexers, psutil, pexpect on POSIX, and colorama on Windows. The 9.x line needs Python 3.11 or newer. No config file exists until you run ipython profile create, which writes ~/.ipython/profile_default/ipython_config.py; until then every setting has to be passed on the command line. The 9.0 color and theme rewrite matters if you are migrating an old config: TerminalInteractiveShell.colors now wants lowercase theme names, and highlighting_style is deprecated with no effect. Notebook support is a separate install.
Patterns
Install and start the shellstart-shell
$ pip install ipython
$ ipython
In [1]: 2 + 2
Out[1]: 4
# equivalent, and always uses the interpreter you point at:
$ python -m IPythonpython -m IPython runs the copy inside the active virtualenv. A globally installed ipython on PATH will happily start with the wrong site-packages and make your project imports vanish.
Inspect anything with ? and ??introspect-object
In [1]: import json
In [2]: json.dumps? # signature, docstring, file, type
In [3]: json.dumps?? # the same plus the source code
In [4]: json.*dump*? # wildcard search over matching names
In [5]: %pdoc json.dumps # docstring only?? quietly falls back to ? for C extensions and anything without Python source, so a missing source block usually means the object is compiled, not that you typed it wrong.
Measure how long something takestime-code
In [1]: %timeit sum(range(1000))
In [2]: %%timeit data = list(range(1000))
...: sorted(data, reverse=True)
...:
In [3]: %time expensive_call() # one run, wall and CPU timeCode on the same line as %%timeit is setup and runs once; the cell body is what gets looped. %timeit reports the best of many runs, so it hides cold-cache and first-call costs that %time will show you.
Run shell commands and capture the outputshell-commands
In [1]: !git status --short
In [2]: pyfiles = !ls *.py # captured as an SList
In [3]: pyfiles.grep('test') # SList adds .grep, .fields, .s, .n, .p
In [4]: name = 'README.md'
In [5]: !wc -l {name} # braces interpolate Python variables!cd somewhere does not change the shell's directory because each ! runs in its own subprocess; use the %cd magic instead.
Pick up edits without restartingautoreload-code
In [1]: %load_ext autoreload
In [2]: %autoreload 2 # reload every imported module before each cell
In [3]: from mylib import handler
In [4]: handler() # edit mylib.py, call again, new code runs
In [5]: %autoreload 0 # back to normal import cachingInstances created from the old class definition keep their old methods, and a module that raises on re-import is skipped with a warning. When behavior stops matching the file on disk, restart rather than debug the reloader.
Debug the exception you just hitpost-mortem-debug
In [1]: run_broken()
ValueError: bad input
In [2]: %debug # pdb at the frame that raised; u/d to walk the stack
In [3]: %pdb on # auto-enter the debugger on every later exception
In [4]: %xmode Verbose # tracebacks that also print local variables%debug only reaches the most recent exception in the session. Run any other cell first and that traceback is gone, so call it immediately.
Execute a script inside the sessionrun-script
In [1]: %run analysis.py # fresh namespace, results land in the shell
In [2]: %run -i analysis.py # reuse the current namespace instead
In [3]: %run -d analysis.py # start under the debugger
In [4]: %run -p analysis.py # run under the profiler%run sets __name__ to '__main__', so any if __name__ == '__main__' block fires. Use a normal import when you only want the module loaded.
Drop an interactive shell into your own programembed-shell
from IPython import embed
def process(order):
total = compute(order)
embed() # full shell here, with order and total in scope
return totalembed() blocks the process and reads the real stdin, so it hangs under pytest output capture, in daemonized workers, and in CI. Gate it behind an environment variable before that code can ship.
Create a config file and set defaultsconfig-profile
$ ipython profile create
# writes ~/.ipython/profile_default/ipython_config.py
# ipython_config.py
c.InteractiveShellApp.extensions = ['autoreload']
c.InteractiveShellApp.exec_lines = ['%autoreload 2']
c.TerminalInteractiveShell.editing_mode = 'vi'
c.TerminalInteractiveShell.confirm_exit = False
c.InteractiveShell.colors = 'linux'Theme names became lowercase in 9.0, so an old 'Linux' value now warns. highlighting_style is deprecated since 9.0 and does nothing; theme configuration replaced it.
Reach earlier inputs and outputsoutput-history
In [1]: 6 * 7
Out[1]: 42
In [2]: _ # last result
In [3]: _1 # result of In [1]; Out is a dict, In a list of source strings
In [4]: %history -n 1-5
In [5]: %save session.py 1-5 # write those inputs to a fileOut keeps a reference to every displayed result, so one big DataFrame stays alive for the whole session. %reset -f out drops the output cache without touching your variables.
Install packages and capture noisy outputinstall-and-capture
In [1]: %pip install httpx # installs into the running interpreter
In [2]: %lsmagic # plain text since 9.16; --json for the mapping
In [3]: %%capture result
...: print('noisy')
...:
In [4]: result.stdout
Out[4]: 'noisy\n'%pip and %conda target the interpreter running the shell. !pip install resolves pip off PATH and regularly installs into a different environment than the one you are typing in.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| ptpython | PyPI | You want the better prompt (completion, multiline, vi mode) with a fraction of the dependency weight and no magics |
| bpython | PyPI | You want an inline-documentation REPL that stays close to plain Python semantics |
| xonsh | PyPI | You want Python and shell in one language as your actual login shell, not just a REPL with ! escapes |