Debugging Refactor Snippets
Copy-paste before/after pairs for defects covered in this section. Each snippet is a minimal, runnable pattern you can drop into a failing codebase and adapt.
Use Cases
- Fix mutable default arguments in shared utilities
- Break circular imports without lazy-import sprawl
- Stop blocking the asyncio event loop in HTTP handlers
- Replace float money math with Decimal or int cents
- Harden pandas merges and NaN handling
- Decode bytes/str at IO boundaries explicitly
- Stabilize environments with uv lockfiles
Simplest Implementation
Start with the one-line or five-line fix (sentinel None, explicit encoding, math.isclose). Ship the smallest change with a test, then refactor structure if the pattern repeats.
Variations
Variation: Mutable default to sentinel
# BEFORE
def add(item, items=[]):
items.append(item)
return items
# AFTER
def add(item, items=None):
if items is None:
items = []
items.append(item)
return itemsVariation: Dataclass default_factory
# BEFORE
from dataclasses import dataclass
@dataclass
class Cart:
lines: list[str] = []
# AFTER
from dataclasses import dataclass, field
@dataclass
class Cart:
lines: list[str] = field(default_factory=list)Variation: Circular import leaf module
# BEFORE
# orders.py imports billing.py; billing.py imports orders.py
# AFTER
# types.py holds shared dataclasses
from dataclasses import dataclass
@dataclass(frozen=True)
class OrderId:
value: strVariation: Blocking call in async route
# BEFORE
async def route():
import time
time.sleep(1)
# AFTER
import asyncio
async def route():
await asyncio.sleep(1)Variation: Sync HTTP in async FastAPI
# BEFORE
async def fetch(url: str):
import urllib.request
return urllib.request.urlopen(url).read()
# AFTER
import httpx
async def fetch(client: httpx.AsyncClient, url: str):
return (await client.get(url)).contentVariation: Float money to Decimal
# BEFORE
total = 19.99 * 3
# AFTER
from decimal import Decimal, ROUND_HALF_UP
total = (Decimal("19.99") * 3).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)Variation: Float equality
# BEFORE
if result == expected_float:
# AFTER
import math
if math.isclose(result, expected_float, rel_tol=1e-9):Variation: Text file encoding
# BEFORE
text = open(path).read()
# AFTER
from pathlib import Path
text = Path(path).read_text(encoding="utf-8")Variation: str + bytes concat
# BEFORE
payload = "prefix" + b"bytes"
# AFTER
payload = "prefix".encode("utf-8") + b"bytes"Variation: pandas merge guard
# BEFORE
merged = left.merge(right, on="id")
# AFTER
merged = left.merge(right, on="id", validate="one_to_one")
assert len(merged) == len(left)Variation: NaN before sum
# BEFORE
total = df["amount"].sum()
# AFTER
total = df.dropna(subset=["amount"])["amount"].sum()Variation: Environment reproducibility
# BEFORE
pip install -r requirements.txt
# AFTER
uv sync --frozen
uv run pytestComplex Implementation
Production-ready pattern combining guardrails for a billing microservice hot path:
from __future__ import annotations
import asyncio
import logging
from dataclasses import dataclass, field
from decimal import Decimal, ROUND_HALF_UP
from typing import Protocol
import httpx
log = logging.getLogger(__name__)
@dataclass(frozen=True)
class Money:
cents: int
@staticmethod
def from_decimal(amount: str, qty: int) -> "Money":
total = (Decimal(amount) * qty).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
return Money(int(total * 100))
class TaxClient(Protocol):
async def rate_for(self, region: str) -> Decimal: ...
class HttpxTaxClient:
def __init__(self, client: httpx.AsyncClient) -> None:
self._client = client
async def rate_for(self, region: str) -> Decimal:
resp = await self._client.get(f"/tax/{region}", timeout=5.0)
resp.raise_for_status()
return Decimal(str(resp.json()["rate"]))
@dataclass
class QuoteService:
tax_client: TaxClient
_cache: dict[str, Decimal] = field(default_factory=dict)
async def quote(self, region: str, unit_price: str, qty: int) -> Money:
subtotal = Money.from_decimal(unit_price, qty)
rate = self._cache.get(region)
if rate is None:
rate = await self.tax_client.rate_for(region)
self._cache[region] = rate
tax = (Decimal(subtotal.cents) / 100 * rate).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
return Money(subtotal.cents + int(tax * 100))
async def main() -> None:
async with httpx.AsyncClient(base_url="https://example.test") as client:
svc = QuoteService(HttpxTaxClient(client))
log.info("quote", extra={"amount": svc.quote("TX", "19.99", 2)})
if __name__ == "__main__":
asyncio.run(main())Key aspects:
- Money stored as int cents avoids float entirely
- Async HTTP uses
httpxwith timeout instead of blocking urllib - Cache uses
default_factory=dict, not mutable default - Tax
Decimalparsed from JSON string, not float - Structured logging hook ready for request correlation
Gotchas
- Copy-paste without tests - snippet fixes another symptom. Fix: add a regression test per snippet applied.
- Partial async conversion - only outer route async while library blocks. Fix: push
to_threador async client to the actual IO call. - Decimal from float JSON -
Decimal(resp.json()["x"])when x is float reintroduces error. Fix: stringify at API or use int cents. - Bounded cache omitted -
_cachedict grows forever in snippet above. Fix: add LRU/TTL when regions are unbounded. - validate=one_to_one too strict - legitimate one-to-many joins fail. Fix: pick correct cardinality after profiling keys.
FAQs
Should I apply all snippets during one refactor?
No. One defect class per PR with tests. Combined refactors obscure which fix solved the issue.
Where do these snippets live in a service repo?
Prefer fixing source modules and capturing patterns in internal docs or this reference. Avoid a growing snippets.py imported in prod.
Can ruff auto-fix any of these?
ruff 0.9+ flags some mutable defaults and debug statements. Encoding and Decimal changes need human review.
How do I verify a snippet fixed production?
Deploy behind metrics: error rate, p95 latency, memory RSS, and business reconciliation totals for data fixes.
Are before blocks safe to run?
Minimal examples are for illustration. Mutable-default before blocks mutate shared state if called repeatedly in one process.
How do snippets interact with type checkers?
After fixes, add types (list[str] | None, Decimal) so mypy/pyright prevent regressions.
What snippet fixes circular imports long term?
Leaf types or protocols module, not permanent lazy imports. Lazy import variation is emergency only.
How do I choose int cents vs Decimal?
Int cents for fixed-scale currency in high throughput. Decimal when tax rules need arbitrary precision before quantize.
Does validate= on merge slow pipelines?
Negligible on moderate data; invaluable during development. Remove only after automated key uniqueness checks exist.
Can I use these in Django views?
Yes. Apply encoding and Decimal patterns in views/services; keep ORM boundary mapping explicit.
What is the fastest snippet for "works on my machine"?
uv sync --frozen plus python -c "import pkg; print(pkg.__file__)" before deeper debugging.
How do snippets relate to Debugging Tools?
Use snippets after pdb/logging identified the defect class. Tools find; snippets fix structurally.
Related
- Mutable Default Argument Bugs - full narrative on shared defaults
- Circular Import Scenarios - cycle diagnosis
- asyncio Deadlocks & Blocking-the-Loop - async stalls
- Floating-Point & Numeric Bugs - numeric detail
- Data Pipeline Defects - pandas/polars guards
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.