AI Skill Report Card
Implementing Tab Completion
Quick Start14 / 15
Pythonimport readline import rlcompleter readline.parse_and_bind("tab: complete")
That's it for the standard interactive interpreter case—rlcompleter.Completer binds against the __main__ namespace by default when used this way (as python -m startup does via site.py / PYTHONSTARTUP).
Recommendation▾
Add a troubleshooting section for common readline binding issues across libedit vs GNU readline (macOS default differs)
Workflow13 / 15
Progress:
- Step 1: Decide the namespace to complete against (global
__main__vs. a custom dict/namespace) - Step 2: Instantiate
rlcompleter.Completer(optionally with a namespace) - Step 3: Register the completer with
readline.set_completer - Step 4: Bind the Tab key via
readline.parse_and_bind - Step 5: Test simple identifier completion and dotted attribute completion
Step 1–4: Custom namespace example
Pythonimport readline import rlcompleter namespace = {"foo": 42, "bar": "hello"} completer = rlcompleter.Completer(namespace) readline.set_completer(completer.complete) readline.parse_and_bind("tab: complete")
Step 5: How completion resolves
complete(text, state)is called repeatedly by readline with increasingstate(0, 1, 2...) until it returnsNone.- No dot in
text→ matches keywords, builtins, and names in the namespace. - Dot in
text(e.g."foo.up") → evaluates the expression before the last dot usingeval()in the namespace, then completes attributes viadir(). This means completion can execute arbitrary code (property getters,__getattr__, etc.).
Recommendation▾
Include an example showing custom completer chaining with existing completions or history search
Examples16 / 20
Example 1: Default interactive interpreter setup
Input: Add tab completion to a script meant to be run with python -i myscript.py
Output:
Pythontry: import readline except ImportError: pass else: import rlcompleter readline.parse_and_bind("tab: complete")
Example 2: Completion inside a sandboxed REPL
Input: Build a mini-REPL that only completes names from a restricted namespace, not __main__
Output:
Pythonimport readline, rlcompleter sandbox_ns = {"__builtins__": {}, "x": [1, 2, 3]} completer = rlcompleter.Completer(sandbox_ns) readline.set_completer(completer.complete) readline.parse_and_bind("tab: complete") while True: line = input(">>> ") exec(line, sandbox_ns)
Example 3: Inspecting raw completion matches (non-interactive/testing)
Input: Programmatically get completions for "li" and "str.up"
Output:
Pythonc = rlcompleter.Completer({"list": list}) i = 0 matches = [] while True: m = c.complete("li", i) if m is None: break matches.append(m) i += 1 # matches -> ['list(', 'list.'] (exact strings vary by Python version) i, attr_matches = 0, [] while True: m = c.complete("str.up", i) if m is None: break attr_matches.append(m) i += 1 # attr_matches -> ['str.upper(']
Recommendation▾
Consider a brief note on performance implications of eval-based completion in large namespaces
Best Practices
- Guard
import readlineintry/except ImportError— it's not available on Windows withoutpyreadline3or similar. - Prefer letting
rlcompleterdefault to__main__.__dict__for general-purpose interactive tools; only pass a custom namespace when you need isolation. - Combine with
atexit+readline.write_history_filefor a complete REPL experience (history + completion). - Use
readline.set_completer_delims()if you need to tweak which characters break "words" for completion (defaults exclude.handling specially insiderlcompleteritself). - Remember completion for dotted names calls
eval()— only wire this up against namespaces where arbitrary attribute access/property evaluation is acceptable.
Common Pitfalls
- Forgetting
parse_and_bind: instantiatingCompleterand callingset_completeralone does nothing until Tab is bound. - Assuming safety:
rlcompleterevaluates expressions to resolve attributes on tab-press, which can trigger side effects (e.g.,@propertymethods with side effects,__getattr__doing I/O). Never expose this against untrusted/attacker-controlled namespaces. - Platform assumption: assuming
readlineis always importable; it's absent by default on Windows. - Confusing module scope: passing a copy of a namespace expecting live updates —
Completerholds a reference to the dict, so mutating the original dict is fine, but replacing it wholesale is not reflected unless you re-instantiate or use aCompleterwith no namespace and rely on__main__. - State parameter misuse: manually calling
complete(text, state), forgetting thatstatemust increment from 0 and stop at the firstNone, not stop at empty string.