Stop guessing. Start understanding.
Your Python errors, explained — in plain English.
pip install butwhy → import butwhy → done.
You know how Python tracebacks look like this:
Traceback (most recent call last):
File "demo.py", line 2, in <module>
result = data.split(",")
AttributeError: 'NoneType' object has no attribute 'split'
And then you spend 5 minutes on Google figuring out what went wrong?
why turns that into this:
============================================================
AttributeError: 'NoneType' object has no attribute 'split'
at demo.py:2
============================================================
[pattern match · 93%]
Summary:
Calling .split() on None
Cause:
The variable data is None, not a string.
This usually means a function returned None (maybe it
forgot a return statement), or a lookup failed silently.
Variables at error:
data = None (NoneType)
Fix:
Add a None check: if data is not None: data.split(",")
Or trace back to find why data is None.
============================================================
One import. Zero config. Instant understanding.
pip install butwhyimport butwhy # That's it. Errors are now explained.
data = None
result = data.split(",") # Boom — why explains itSet one environment variable and why upgrades automatically:
export OPENAI_API_KEY=sk-... # or ANTHROPIC_API_KEYNo API key? why still works — it falls back to built-in pattern matching that covers 12+ common error types with zero dependencies.
# Install Ollama: https://ollama.com
ollama pull qwen2.5-coder:7b
export BUTWHY_PROVIDER=ollamaimport butwhy
# Every uncaught exception in your script is now explained
1 / 0import butwhy
with butwhy.trace():
# Only errors inside this block are explained
risky_operation()import butwhy
@butwhy.explain
def divide(a, b):
return a / b
divide(10, 0) # Error is explained, then re-raisedAfter an error, call butwhy.fix() to get an AI-generated patch:
import butwhy
data = None
result = data.split(",") # crashes
# In interactive mode:
>>> butwhy.fix()
Suggested fix for demo.py:2
--------------------------------------------------
- result = data.split(",")
+ if data is not None:
+ result = data.split(",")
+ else:
+ result = []
--------------------------------------------------
Apply this fix? [y/N] y
Applied fix to demo.py:2Requires an AI provider (OpenAI/Anthropic/Ollama).
>>> import butwhy
>>> butwhy.thisThe Zen of Why, by why
Errors are not failures, they are teachers.
The traceback tells you what; why tells you why.
A good message answers the question before you ask it.
Read the variables, not just the line numbers.
...
# In a notebook:
%load_ext why
# Or just:
import butwhy
# Now all cell errors are explained inlineAll settings via environment variables — no config file needed:
| Variable | Default | Description |
|---|---|---|
OPENAI_API_KEY |
— | OpenAI API key (enables AI explanations) |
ANTHROPIC_API_KEY |
— | Anthropic API key (enables AI explanations) |
BUTWHY_PROVIDER |
auto |
auto / openai / anthropic / ollama / patterns |
BUTWHY_LANGUAGE |
en |
en / zh (Chinese) |
BUTWHY_MODEL |
— | Override the model name for the active provider |
BUTWHY_TIMEOUT |
30 |
API timeout in seconds |
OLLAMA_URL |
http://localhost:11434 |
Ollama server URL |
OLLAMA_MODEL |
qwen2.5-coder:7b |
Ollama model to use |
BUTWHY_AUTOSTART |
1 |
Set to 0 to disable auto-install of the hook |
BUTWHY_NO_COLOR |
— | Set to disable colored output |
NO_COLOR |
— | Standard no-color env var (respected) |
import butwhy
butwhy.set_config(butwhy.WhyConfig(
provider="openai",
openai_api_key="sk-...",
language="zh", # Chinese explanations
show_source=True,
show_vars=True,
cache=True,
))why uses a three-layer fallback to ensure you always get a useful explanation:
| Layer | When | How | Quality |
|---|---|---|---|
| AI explanation | API key available | Sends error + source context + variables to LLM | Best — covers everything, gives specific fixes |
| Pattern matching | No API key (or AI failed) | Heuristic matching on 12+ common error types | Good — covers the errors you hit daily |
| Enhanced traceback | No pattern matched | Colored output with variables and source context | Fallback — still better than default |
Key design principles:
- Zero dependencies — pure Python stdlib. No rich, no httpx, no openai SDK. Just
urlliband friends. - Never crashes — if the AI fails, pattern matching takes over. If that fails, you still get a prettier traceback.
- Privacy-aware — your code only goes to an LLM if you explicitly set an API key. Pattern matching is 100% local.
- Caching — repeated errors don't re-call the API. Cached for 7 days.
| Feature | why |
rich | better-exceptions | stackprinter | Copilot |
|---|---|---|---|---|---|
| Works without API key | ✅ | ✅ | ✅ | ✅ | ❌ |
| AI-powered explanations | ✅ | ❌ | ❌ | ❌ | ✅ |
import and done |
✅ | ❌ | ❌ | ❌ | ❌ |
| Local model support | ✅ Ollama | — | — | — | ❌ |
| Zero dependencies | ✅ | ❌ | ❌ | ❌ | — |
| Variable values in output | ✅ | ✅ | ✅ | ✅ | ❌ |
Auto-fix (butwhy.fix()) |
✅ | ❌ | ❌ | ❌ | ✅ |
| Works in CI/CD & servers | ✅ | ✅ | ✅ | ✅ | ❌ |
| Chinese explanations | ✅ | ❌ | ❌ | ❌ | ✅ |
| Jupyter magic | ✅ | ❌ | ❌ | ❌ | ❌ |
The built-in pattern matcher covers these error types without any API key:
NameError— undefined variableTypeError— wrong type operationsValueError— invalid values (int conversion, unpacking)IndexError— list index out of rangeKeyError— missing dictionary keyAttributeError— wrong attribute/method (including NoneType)ZeroDivisionError— division by zeroImportError/ModuleNotFoundError— missing modules (with install hints)FileNotFoundError— file path issuesSyntaxError— common syntax mistakesRecursionError— infinite recursionUnicodeDecodeError— encoding issuesIndentationError/TabError— indentation problems
More patterns are added regularly. With an AI provider, all error types are covered.
See the examples/ directory for runnable demos:
basic.py— common errors with pattern matchingwith_ai.py— AI-powered deep explanationsjupyter_demo.ipynb— notebook usagefix_demo.py— auto-fix workflow
Contributions welcome! Especially:
- New pattern matchers for error types not yet covered
- Translations for
BUTWHY_LANGUAGE - Bug reports and edge cases
git clone https://github.com/BaolinGao/butwhy.git
cd butwhy
pip install -e ".[dev]"
pytestMIT — see LICENSE.
import butwhy — because every error deserves an explanation.