Python readline: History and Autocomplete

Published on: August 25, 2026
Reading time: 5 minutes
A developer typing code on a laptop with a Python book beside in an office.

The readline module adds line editing, command history, and tab completion to interactive terminal programs. It can affect the traditional Python prompt and ordinary input() calls, allowing users to navigate with arrow keys, reuse previous entries, and complete words without retyping them.

Despite its name, the module may be backed by GNU Readline or the compatible libedit implementation. That difference affects configuration files, key bindings, history formats, and small API details. Modern Python also introduces another distinction: the new REPL added in Python 3.13 does not use readline by default.

Availability

readline is optional and normally available on Unix. It is not supported on Android, iOS, or WASI. A distributor may build CPython without it, so portable software should handle ImportError.

try:
    import readline
except ImportError:
    readline = None

Windows may use third-party alternatives, but those are not the standard module. Do not make terminal convenience features a hard dependency when the main application can still run without them.

Detect the backend

Since Python 3.13, readline.backend reports either readline or editline.

import readline

print(readline.backend)

macOS commonly uses libedit. GNU Readline normally reads ~/.inputrc, while libedit commonly reads ~/.editrc. Their command syntax is different, and their history files may not be interchangeable.

Enable Tab completion

The simplest setup uses rlcompleter, which completes Python names in the current namespace.

import readline
import rlcompleter

if readline.backend == "editline":
    readline.parse_and_bind("bind ^I rl_complete")
else:
    readline.parse_and_bind("tab: complete")

The guide to Python rlcompleter covers identifier completion in more detail. Custom command applications usually need their own completer.

Write a custom completer

A completer receives text and state. It returns one match for each state value and eventually returns None.

import readline

COMMANDS = ["configure", "help", "list", "open", "quit", "run"]

def complete(text, state):
    matches = [item for item in COMMANDS if item.startswith(text)]
    if state < len(matches):
        return matches[state]
    return None

readline.set_completer(complete)
readline.set_completer_delims(" \t\n")
readline.parse_and_bind("tab: complete")

Keep completion fast. A function that performs a network request, scans a huge directory, or queries a database on every Tab press creates a poor interface.

Completion delimiters

set_completer_delims() controls which characters split the current word. File paths, dotted names, and colon-separated values may require removing characters from the default set.

delimiters = readline.get_completer_delims()
readline.set_completer_delims(delimiters.replace("/", ""))

get_begidx() and get_endidx() expose the active completion range. GNU Readline and libedit can return different indexes for similar editing states, so cross-platform tests are important.

In-memory history

The global history list can be inspected and modified.

import readline

readline.add_history("status")
readline.add_history("list projects")

count = readline.get_current_history_length()
for index in range(1, count + 1):
    print(index, readline.get_history_item(index))

get_history_item() uses one-based indexes. remove_history_item() and replace_history_item() use zero-based positions. Mixing those conventions is a common bug.

Do not store secrets

Tokens, passwords, private keys, recovery codes, and personal data must not be written to command history. Use getpass for passwords and disable automatic history around sensitive prompts.

import getpass
import readline

readline.set_auto_history(False)
try:
    secret = getpass.getpass("Token: ")
finally:
    readline.set_auto_history(True)

The setting is process-global. Restore it after errors and never log the captured value.

Load and save a history file

A persistent file makes commands available across sessions.

import atexit
import os
import readline

history_path = os.path.expanduser("~/.my_app_history")

try:
    readline.read_history_file(history_path)
except FileNotFoundError:
    pass

readline.set_history_length(1000)
atexit.register(readline.write_history_file, history_path)

Python 3.14 adds auditing events to history and initialization-file access. Security hooks may record or reject these operations.

Protect the history file

History often reveals paths, hostnames, arguments, and operational details. Create it in a private directory and use restrictive permissions where the platform supports them.

from pathlib import Path
import os

path = Path.home() / ".my_app_history"
if not path.exists():
    fd = os.open(path, os.O_CREAT | os.O_WRONLY, 0o600)
    os.close(fd)

Unix mode bits are not a universal security model. Windows access control requires different assumptions.

Concurrent sessions

write_history_file() overwrites the destination. Two open sessions can lose each other’s commands. When available, append_history_file() appends only new entries.

import atexit
import readline

starting_count = readline.get_current_history_length()

def save_incremental(path):
    current = readline.get_current_history_length()
    new_items = max(0, current - starting_count)
    readline.set_history_length(1000)
    if new_items:
        readline.append_history_file(new_items, path)

atexit.register(save_incremental, history_path)

Appending still does not guarantee perfect coordination between simultaneous writers. Use file locking or per-session history when consistency matters.

Limit growth

set_history_length() controls the number of lines saved. A negative value means unlimited history and may create an ever-growing file.

Choose a practical line limit and, for tools with large commands, consider a byte-size limit or rotation policy.

Modify the current line buffer

get_line_buffer() returns the current input. insert_text() inserts at the cursor, and redisplay() refreshes the screen.

import readline


def add_default_text():
    if not readline.get_line_buffer():
        readline.insert_text("list ")
        readline.redisplay()

readline.set_startup_hook(add_default_text)

A startup hook runs before the first prompt is printed. A pre-input hook, when supported, runs after the prompt and before character input begins.

Avoid unsafe automatic insertion

Text inserted into a prompt may look ready for execution. Do not construct commands from untrusted remote content, filenames, or pasted data without validation. The user should retain control before pressing Enter.

Configuration files

read_init_file() loads a configuration file, while parse_and_bind() applies one line directly.

if readline.backend == "readline":
    readline.parse_and_bind("set editing-mode vi")
else:
    readline.parse_and_bind("bind -v")

Do not automatically load a configuration file from an untrusted location. Readline configuration can define macros and change important key behavior.

Use with input()

After importing and configuring the module, regular input() calls gain editing and history behavior.

while True:
    try:
        command = input("app> ").strip()
    except EOFError:
        break
    except KeyboardInterrupt:
        print()
        continue

    if command == "quit":
        break
    execute(command)

Handle EOF and Ctrl+C without exposing a stack trace. The guide to Python signal explains interruption and cooperative shutdown.

The new Python REPL

The current documentation notes that the new REPL introduced in Python 3.13 does not use readline. Set PYTHON_BASIC_REPL to use the basic compatible REPL.

This limitation applies to the interpreter’s new interactive prompt. Applications that call input() can still configure readline normally.

Testing

Test GNU Readline and libedit, missing-module behavior, absent and read-only history files, concurrent sessions, Unicode input, zero and multiple completion matches, Ctrl+C, EOF, and non-interactive terminals.

Pseudo-terminal tests can emulate a real TTY. A later guide in this batch covers the Unix pty module.

Common mistakes

Common failures include assuming GNU Readline on macOS, sharing GNU and libedit configuration, storing secrets, leaving unlimited history, overwriting concurrent sessions, running slow operations in the completer, confusing history index bases, and requiring the module on unsupported platforms.

Conclusion

readline turns basic prompts into productive terminal interfaces with editing, history, and completion. Reliable use requires backend detection, secure history handling, bounded storage, and graceful fallback.

Keep completers fast, protect history files, and test both GNU Readline and libedit. Consult the official readline documentation and the GNU Readline manual.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Python asynchronous code on a laptop for inspect.markcoroutinefunction
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    markcoroutinefunction: Detect Async Wrappers

    Learn inspect.markcoroutinefunction in Python to identify async wrappers, integrate frameworks, and avoid incorrect coroutine detection.

    Ler mais

    Tempo de leitura: 5 minutos
    10/10/2026
    Python code for traversing folders and files with Path.walk
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Path.walk: Traverse Directories Safely

    Learn Python Path.walk to traverse directories, filter files, handle errors, and control directory-tree processing safely.

    Ler mais

    Tempo de leitura: 5 minutos
    10/10/2026
    Python process debugging in a terminal with source code
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    pdb -p: Debug Running Python Processes

    Learn how to attach pdb to a running Python process, inspect stacks, and diagnose hangs safely.

    Ler mais

    Tempo de leitura: 6 minutos
    09/10/2026
    Python programming code for exact fractions
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    fractions.from_number: Convert Numbers to Fractions

    Learn Python fractions.from_number for exact rational conversion, float and Decimal handling, approximation, validation, and safe arithmetic.

    Ler mais

    Tempo de leitura: 4 minutos
    09/10/2026
    Developer configuring a Python HTTPS server and TLS certificate
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: Build a Local HTTPS Server in Python

    Learn Python HTTPSServer for local HTTPS services, TLS certificates, threaded handling, testing, and practical security limits.

    Ler mais

    Tempo de leitura: 4 minutos
    08/10/2026
    Protected files representing secure TAR extraction with Python
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: Extract TAR Safely

    Learn Python tarfile extraction_filter for safer TAR extraction, path validation, links, permissions, and resource limits.

    Ler mais

    Tempo de leitura: 5 minutos
    08/10/2026