Python#

Shortcut keys for the Python interactive interpreter. CPython on Linux / macOS uses GNU readline at the REPL, so the readline emacs keymap is in play. CPython 3.13+ ships a new PyREPL by default (the same line editor as PyPy) which adds multi-line editing, syntax highlighting, and an interactive history pager.

For richer interactive Python, IPython has its own bindings (prompt_toolkit), and pdb / ipdb rebind a few keys for debugger control. For setup, virtual environments, and packaging, see Python.

Cursor Movement (REPL)#

Key

Action

Ctrl-a

move to start of line

Ctrl-e

move to end of line

Ctrl-b / Left

move back one character

Ctrl-f / Right

move forward one character

Alt-b

move back one word

Alt-f

move forward one word

Editing (REPL)#

Key

Action

Ctrl-d

delete character under cursor (EOF on empty line, exits)

Backspace / Ctrl-h

delete character before cursor

Alt-d

delete word forward

Ctrl-w

delete word backward

Ctrl-k

kill to end of line

Ctrl-u

kill to start of line

Ctrl-y

yank (paste) last killed text

Ctrl-t

transpose characters

Alt-t

transpose words

Alt-u / Alt-l / Alt-c

uppercase / lowercase / capitalize word

Ctrl-_

undo

History (REPL)#

CPython reads / writes ~/.python_history when readline is linked. Configure with readline.read_history_file / write_history_file in $PYTHONSTARTUP.

Key

Action

Up / Ctrl-p

previous history entry

Down / Ctrl-n

next history entry

Ctrl-r

incremental reverse search

Ctrl-s

incremental forward search (stty -ixon required)

Alt-.

insert last argument of previous line

readline.get_history_length()

inspect history at runtime

readline.clear_history()

clear current-session history

PyREPL (3.13+)#

The new default interpreter in 3.13. Bindings overlap with the readline set; the additions are multi-line editing, paste mode, and a few discovery shortcuts.

Key

Action

F1

enter REPL help / commands menu

F2

browse history with a pager

F3

toggle paste mode (multi-line paste without auto-indent)

Ctrl-x Ctrl-e

edit current multi-line buffer in $EDITOR

Up / Down

move between lines in a multi-line block (not history)

Ctrl-Up / Ctrl-Down

previous / next history block

Tab

completion (rlcompleter)

exit / quit

leave the REPL (no parens needed in PyREPL)

Process Control#

Key

Action

Ctrl-c

raise KeyboardInterrupt

Ctrl-d

send EOF (exits the REPL on empty line)

Ctrl-l

clear screen

Ctrl-z

suspend interpreter (SIGTSTP)

Ctrl-\

send SIGQUIT (core dump)

IPython#

IPython (and Jupyter terminal) replaces readline with prompt_toolkit. Many bindings overlap, but the magic / introspection helpers are IPython-only.

Key

Action

Tab

completion menu (paginated)

Shift-Tab

inspect object at cursor (signature + docstring tip)

? after a name

print docstring (e.g. len?)

?? after a name

print source (e.g. foo??)

Ctrl-r

reverse history search

Alt-Enter / Esc Enter

insert newline without submitting (multi-line)

Ctrl-o

insert newline (alt binding)

Ctrl-Enter

submit current cell (where the terminal supports it)

Ctrl-c

cancel current line (does not exit IPython)

Ctrl-d

exit IPython

%hist

print session history (IPython magic)

%edit

open buffer in $EDITOR

%paste

paste from clipboard and execute

%cpaste

paste a block with explicit terminator (--)

pdb / ipdb#

Debugger-specific single-letter commands (typed, then Enter).

Command

Action

h

help

l / ll

list around current line / list whole function

n

next (step over)

s

step (into function)

r

return from current function

c

continue until next breakpoint

b <file>:<line>

set breakpoint

cl <bp>

clear breakpoint

p <expr>

print expression

pp <expr>

pretty-print expression

w

print stack trace

u / d

move up / down the stack

q

quit debugger

Ctrl-d

same as q

!<stmt>

run an arbitrary Python statement in the current frame

import IPython; IPython.embed()

drop into IPython mid-frame (ipdb)

Config and Discovery#

Command / file

Action

$PYTHONSTARTUP

path to a file executed at REPL start (history, completion)

~/.python_history

default history file

readline.parse_and_bind("tab: complete")

enable Tab completion (rlcompleter)

rlcompleter

module that powers Tab completion

~/.inputrc

readline config (applies system-wide, not just Python)

PYTHON_BASIC_REPL=1

force the old REPL (disable PyREPL) in 3.13+

ipython profile create

generate IPython config

~/.ipython/profile_default/ipython_config.py

IPython config (key bindings, prompt, etc.)

Read / Write Files#

from pathlib import Path

text = Path("input.txt").read_text(encoding="utf-8")
data = Path("input.json").read_text(encoding="utf-8")

Path("output.txt").write_text("hello\n", encoding="utf-8")

for line in Path("input.txt").read_text(encoding="utf-8").splitlines():
    process(line)

# Iterating large files line-by-line
with open("big.txt", encoding="utf-8") as f:
    for line in f:
        process(line)

Read / Write JSON#

import json
from pathlib import Path

data = json.loads(Path("in.json").read_text())

Path("out.json").write_text(json.dumps(data, indent=2, ensure_ascii=False))

Walk a Directory#

from pathlib import Path

for p in Path(".").rglob("*.py"):
    if p.is_file():
        print(p)

# Filter by mtime
import time
cutoff = time.time() - 7 * 86400
recent = [p for p in Path(".").rglob("*") if p.stat().st_mtime > cutoff]

Dataclasses#

from dataclasses import dataclass, field
from datetime import datetime

@dataclass(slots=True, frozen=True)
class Person:
    name: str
    age: int = 0
    tags: list[str] = field(default_factory=list)
    created_at: datetime = field(default_factory=datetime.utcnow)

Group By Key#

from collections import defaultdict

by_team = defaultdict(list)
for u in users:
    by_team[u.team].append(u)

Counter#

from collections import Counter

words = Counter(text.split())
words.most_common(10)

LRU Cache#

from functools import cache, lru_cache

@cache                           # unbounded; Python 3.9+
def fib(n: int) -> int:
    return n if n < 2 else fib(n - 1) + fib(n - 2)

@lru_cache(maxsize=1024)         # bounded
def lookup(key: str) -> str:
    return expensive(key)

Context Manager#

from contextlib import contextmanager
import time

@contextmanager
def timed(label):
    start = time.perf_counter()
    try:
        yield
    finally:
        print(f"{label}: {time.perf_counter() - start:.3f}s")

with timed("parse"):
    parse(big_input)

Retry With Backoff#

import time
from typing import Callable, TypeVar

T = TypeVar("T")

def retry(fn: Callable[[], T], attempts: int = 5, base: float = 0.5) -> T:
    last = None
    for i in range(attempts):
        try:
            return fn()
        except Exception as e:
            last = e
            time.sleep(base * 2 ** i)
    raise last

Async HTTP With httpx#

import asyncio, httpx

async def fetch_all(urls: list[str]) -> list[str]:
    async with httpx.AsyncClient(timeout=10) as c:
        rs = await asyncio.gather(*(c.get(u) for u in urls))
        return [r.text for r in rs]

Subprocess#

import subprocess

r = subprocess.run(
    ["git", "rev-parse", "HEAD"],
    capture_output=True, text=True, check=True,
)
print(r.stdout.strip())

Argparse#

import argparse
from pathlib import Path

def main() -> None:
    p = argparse.ArgumentParser()
    p.add_argument("input", type=Path)
    p.add_argument("--limit", type=int, default=100)
    p.add_argument("--verbose", "-v", action="store_true")
    args = p.parse_args()

    run(args.input, args.limit, args.verbose)

Pydantic Settings#

from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_file=".env", env_prefix="APP_")
    host: str = "0.0.0.0"
    port: int = 8080
    database_url: str

settings = Settings()

Logging#

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s %(message)s",
)
log = logging.getLogger(__name__)
log.info("starting")

Idiomatic Comprehensions#

Filter and map in one expression.

evens = [x * x for x in xs if x % 2 == 0]

Invert a dict in one expression.

inv = {v: k for k, v in d.items()}

Flatten a list of lists with itertools.chain.

from itertools import chain
flat = list(chain.from_iterable(lists))

Sliding-window pairs with itertools.pairwise (Python 3.10+).

from itertools import pairwise
diffs = [b - a for a, b in pairwise(xs)]