mrkeyoor.com_
Thu 06 Aug 05:52 UTC
PyPICLI & Toolingupdated 06 Aug 2026

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.

Verdict

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.

API stability4/5Magics, introspection, and the prompt have behaved the same for a decade, but internals move: 9.0 deleted everything deprecated before 8.16, and 9.16 removed IPCompleter.python_matches, OInfo.get, and IPython.utils.py3compat while changing %lsmagic to plain text output
Docs4/5ipython.readthedocs.io covers the shell, magics, config system, and embedding, and every release ships a detailed whatsnew page with linked PRs and migration notes; the configuration surface is still large enough that finding the right trait name usually means searching
Maintenance5/59.16.1 landed on 2026-08-03 with a push the next day, the project follows SPEC-0 for Python support, and Python 3.11 support is explicitly funded by the D. E. Shaw group; the 1279 open issues reflect a very wide surface rather than neglect
Ecosystem5/5Around 40M downloads a week, it is the execution core under ipykernel and therefore Jupyter, and third-party magics ship with tools like line_profiler, memory_profiler, and SQL clients

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
Skip it if

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 IPython

python -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 time

Code 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 caching

Instances 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 total

embed() 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 file

Out 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

PackageRegistryPick it when
ptpythonPyPIYou want the better prompt (completion, multiline, vi mode) with a fraction of the dependency weight and no magics
bpythonPyPIYou want an inline-documentation REPL that stays close to plain Python semantics
xonshPyPIYou want Python and shell in one language as your actual login shell, not just a REPL with ! escapes