feat: Add 8 domain papers and RULEBOOK.md
Domain papers distilled from python-numbers-everyone-should-know: - async-overhead: 1,400x sync vs async overhead - collection-membership: 200x set vs list at 1000 items - json-serialization: 8x orjson vs stdlib - exception-flow: 6.5x exception overhead (try/except free) - string-formatting: f-strings > % > .format() - memory-slots: 69% memory reduction with __slots__ - import-optimization: 100ms+ for heavy packages - database-patterns: 98% commit overhead in SQLite RULEBOOK.md: ~200 token distillation for coding subagents 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
111
papers/exception-flow.md
Normal file
111
papers/exception-flow.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Exception Flow: Performance Patterns
|
||||
|
||||
**Domain:** Exception handling overhead
|
||||
**Source:** python-numbers-everyone-should-know benchmarks (Python 3.14.2, Apple Silicon)
|
||||
|
||||
---
|
||||
|
||||
## TL;DR
|
||||
|
||||
- **try/except with no exception**: Nearly free (1.1 ns overhead)
|
||||
- **Raising an exception**: 6.5x slower than the happy path (139 ns vs 21.5 ns)
|
||||
- **EAFP is fine when exceptions are rare** (<5% of calls)
|
||||
- **Use LBYL for expected failures** (dict key lookup, file existence)
|
||||
- **Never use exceptions for normal control flow**
|
||||
|
||||
---
|
||||
|
||||
## The Numbers
|
||||
|
||||
### Happy Path (No Exception Raised)
|
||||
|
||||
| Operation | Time | Overhead vs Baseline |
|
||||
|-----------|------|---------------------|
|
||||
| Function call (no try/except) | 20.4 ns | baseline |
|
||||
| try/except (no exception raised) | 21.5 ns | +1.1 ns (+5%) |
|
||||
| try/except ValueError (specific) | 22.9 ns | +2.5 ns (+12%) |
|
||||
| try/except/finally | 22.1 ns | +1.7 ns (+8%) |
|
||||
|
||||
**Key insight:** The try block itself is essentially free.
|
||||
|
||||
### Sad Path (Exception Raised)
|
||||
|
||||
| Operation | Time | Slowdown vs Happy Path |
|
||||
|-----------|------|----------------------|
|
||||
| raise + catch ValueError | 139 ns | **6.5x slower** |
|
||||
| raise + catch (base Exception) | 140 ns | 6.5x slower |
|
||||
| raise + catch custom exception | 146 ns | 6.8x slower |
|
||||
| raise + catch with `as e` | 148 ns | 6.9x slower |
|
||||
|
||||
**Key insight:** The 6.5x overhead comes from:
|
||||
1. Creating the exception object (~40 ns)
|
||||
2. Capturing the traceback (~70 ns)
|
||||
3. Stack unwinding and handler lookup (~30 ns)
|
||||
|
||||
---
|
||||
|
||||
## EAFP vs LBYL: When to Use Which
|
||||
|
||||
### EAFP (Easier to Ask Forgiveness than Permission)
|
||||
|
||||
```python
|
||||
try:
|
||||
value = data[key]
|
||||
except KeyError:
|
||||
value = default
|
||||
```
|
||||
|
||||
**Use when:** Exceptions are rare (<5% of calls)
|
||||
|
||||
### LBYL (Look Before You Leap)
|
||||
|
||||
```python
|
||||
if key in data:
|
||||
value = data[key]
|
||||
else:
|
||||
value = default
|
||||
```
|
||||
|
||||
**Use when:** The failure case is common (>15% of calls)
|
||||
|
||||
### Crossover Point
|
||||
|
||||
**Rule of thumb:** If exceptions occur more than 15% of the time, use LBYL.
|
||||
|
||||
### dict.get() Beats Both
|
||||
|
||||
```python
|
||||
# Best: Use .get() - 26.3 ns, no exception possible
|
||||
config = settings.get('database', {})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Practical Rules for Coding Agents
|
||||
|
||||
1. **try/except blocks are free** - don't avoid them for performance
|
||||
2. **Raising exceptions costs 6.5x** - only raise for truly exceptional cases
|
||||
3. **Use .get() for dicts** - beats both EAFP and LBYL
|
||||
4. **Return Optional for expected missing** - not exceptions
|
||||
5. **EAFP for file ops** - TOCTOU protection matters more than perf
|
||||
6. **LBYL when failures are common** (>15% of calls)
|
||||
7. **Never use exceptions for control flow**
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| Scenario | Recommendation |
|
||||
|----------|----------------|
|
||||
| Exception rate <5% | EAFP (try/except) |
|
||||
| Exception rate >15% | LBYL (check first) |
|
||||
| Dict key lookup | Use `.get()` |
|
||||
| Optional return value | Return `None`, not exception |
|
||||
| File operations | EAFP (TOCTOU protection) |
|
||||
| Control flow | Never use exceptions |
|
||||
|
||||
**The core insight:** try/except is free; raising is not. Design APIs to minimize raises, not to avoid try blocks.
|
||||
|
||||
---
|
||||
|
||||
*Benchmark source: python-numbers-everyone-should-know*
|
||||
Reference in New Issue
Block a user