butwhy
Stop guessing. Start understanding.
Your Python errors, explained — in plain English.
pip install butwhy → import butwhy → done.
The 30-second pitch
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.
Quick start
pip install butwhy
import butwhy # That's it. Errors are now explained.
data = None
result = data.split(",") # Boom — why explains it
Want AI-powered deep explanations?
Set one environment variable and why upgrades automatically:
export OPENAI_API_KEY=sk-... # or ANTHROPIC_API_KEY
No API key? why still works — it falls back to built-in pattern matching that covers 12+ common error types with zero dependencies.
Prefer a local model? (No API key, fully offline)
# Install Ollama: https://ollama.com
ollama pull qwen2.5-coder:7b
export BUTWHY_PROVIDER=ollama
Three ways to use why
1. Global (recommended) — import butwhy
import butwhy
# Every uncaught exception in your script is now explained
1 / 0
2. Context manager — with butwhy.trace()
import butwhy
with butwhy.trace():
# Only errors inside this block are explained
risky_operation()
3. Decorator — @butwhy.explain
import butwhy
@butwhy.explain
def divide(a, b):
return a / b
divide(10, 0) # Error is explained, then re-raised
butwhy.fix() — Don't just explain, fix
After 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:2
Requires an AI provider (OpenAI/Anthropic/Ollama).
butwhy.this — The Zen of Why
>>> import butwhy
>>> butwhy.this
The 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.
...
Jupyter / IPython support
# In a notebook:
%load_ext why
# Or just:
import butwhy
# Now all cell errors are explained inline
Configuration
All 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) |
Programmatic config
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,
))
How it works
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.
Comparison
| 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 | ✅ | ❌ | ❌ | ❌ | ❌ |
Supported error types (pattern matching)
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.
Examples
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
Contributing
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/yourname/butwhy.git
cd butwhy
pip install -e ".[dev]"
pytest
License
MIT — see LICENSE.
import butwhy — because every error deserves an explanation.
Metadata
Release files for butwhy 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| butwhy-0.1.0.tar.gz | 32.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| butwhy-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 63.9 kB
Release files / butwhy-0.1.0.tar.gz
| Download URL | butwhy-0.1.0.tar.gz |
|---|---|
| Size | 32.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
601f69ff33898557cc55c4d9cde693cd8a7b155d0b54610f4986049b5a065bab
|
|
BLAKE2b-256 checksum How to use checksums |
1760df5d4a09bf4f8a195f83c8305989a749a226272e89f57bcc243b30810b10
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.12
|
Release files / butwhy-0.1.0-py3-none-any.whl
| Download URL | butwhy-0.1.0-py3-none-any.whl |
|---|---|
| Size | 31.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
635f50318b7746f34bd4a2c625712abd61db7b19cfc46193a38166373e038060
|
|
BLAKE2b-256 checksum How to use checksums |
4a43b8510f4d6726501513c1b8d4df8935cee8ad2076422cc60292e66ea6fec2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.12
|