Common Python Pitfalls¶
Status: 🟢 Active | Owner: Python Guild
Mutable Default Arguments¶
Default argument values are evaluated once at function definition time, not on each call.
# ❌ The same list is shared across all calls
def append_item(item: str, items: list[str] = []) -> list[str]:
items.append(item)
return items
append_item("a") # ["a"]
append_item("b") # ["a", "b"] ← unexpected
# ✅ Use None and create fresh inside the function
def append_item(item: str, items: list[str] | None = None) -> list[str]:
if items is None:
items = []
items.append(item)
return items
Late Binding in Closures¶
Lambda and nested functions bind variables at call time, not definition time.
# ❌ All lambdas capture the same `i` from the loop's final value
funcs = [lambda: i for i in range(5)]
funcs[0]() # 4, not 0
# ✅ Capture by default argument
funcs = [lambda i=i: i for i in range(5)]
funcs[0]() # 0
Catching Broad Exceptions¶
# ❌ Hides bugs — catches KeyboardInterrupt, SystemExit, and everything else
try:
process()
except Exception:
pass
# ✅ Catch only what you can handle
try:
process()
except (ValueError, OrderNotFoundError) as exc:
logger.warning("Processing failed: %s", exc)
raise
Using assert for Runtime Validation¶
assert is stripped when Python runs with the -O flag (optimise mode), which is often set in production.
# ❌ Not guaranteed to run in production
assert user_id is not None, "user_id required"
# ✅ Use an explicit check
if user_id is None:
raise ValueError("user_id is required")
Sync I/O Inside Async Functions¶
Calling blocking I/O directly in an async function blocks the entire event loop.
# ❌ Blocks the event loop during HTTP request
async def fetch_data(url: str) -> dict:
response = requests.get(url) # synchronous!
return response.json()
# ✅ Use an async HTTP client
async def fetch_data(url: str) -> dict:
async with httpx.AsyncClient() as client:
response = await client.get(url)
return response.json()
Importing at Function Scope¶
Placing imports inside function bodies defers ImportError until runtime and defeats static analysis.
# ❌ Import inside function body
def process():
import pandas as pd
return pd.DataFrame()
# ✅ Top-level imports
import pandas as pd
def process():
return pd.DataFrame()
__init__.py Circular Imports¶
Avoid importing from sibling modules in __init__.py files. This is the most common source of circular import errors in larger packages.
References¶
Last reviewed: 2025-Q4 | Owner: Python Guild