71. How not to write Python: an anti-pattern catalog
| Anti-pattern | Why it hurts | Instead |
|---|---|---|
def f(x=[]) |
shared mutable default (Ch 2.6) | x=None, create inside |
except: / except Exception: pass |
hides bugs, eats Ctrl-C | catch specific exceptions; log with traceback at boundaries (Ch 7.2) |
from module import * |
unknown names, shadowing | explicit imports |
if x == None, if len(xs) == 0 |
unidiomatic, == can be overridden |
if x is None, if not xs |
for i in range(len(xs)): xs[i] |
noisy, error-prone | for x in xs, enumerate, zip |
x in big_list in a loop |
O(n) each time | a set (Ch 6.1) |
s += piece in a loop |
quadratic string building | "".join(parts) |
requests.get(url) with no timeout |
hangs forever (Ch 43.1, 64.3) | timeout=(3, 10), a Session |
verify=False, ssl.CERT_NONE |
anyone can intercept | fix the CA bundle (Ch 45.1) |
shell=True with input |
command injection (Ch 34.1) | an argument list |
pickle.load / yaml.load / torch.load(weights_only=False) on untrusted files |
remote code execution (Ch 47) | JSON, safe_load, safetensors |
| f-strings in SQL | SQL injection (Ch 49.1) | placeholders |
random for tokens |
predictable | secrets (Ch 48.1) |
== to compare secrets |
timing leak | hmac.compare_digest |
naive datetime.now() across systems |
wrong by hours | aware UTC datetimes (Ch 19.1) |
| a Python loop over numbers in a hot path | 100× slower | NumPy/pandas/PyTorch vectorization (Ch 53) |
time.sleep / requests inside async def |
blocks every request (Ch 29.5) | await asyncio.sleep, httpx.AsyncClient, to_thread |
bare asyncio.create_task and forget |
lost exceptions, leaked tasks | TaskGroup (Ch 29.2) |
| threads for CPU-bound pure Python (GIL build) | no speedup | processes, or vectorize (Ch 27–28) |
a huge utils.py |
everything depends on everything | modules by responsibility (Ch 9) |
| deep inheritance for code reuse | rigid, hard to follow | composition, protocols (Ch 17) |
classes with only __init__ and one method |
Java in Python | a function, or a dataclass |
| unpinned dependencies in an app | different program each install (Ch 21.4) | uv.lock + --locked |
| notebooks as production code | hidden state, no tests | modules + tests; notebooks import them (Ch 62.3) |
print debugging left in production |
noise, leaked data | logging with levels (Ch 68) |
| model or data loaded at import time | slow imports, surprise network calls | load in main() or a lifespan hook (Ch 44) |
| fitting preprocessing on all data before the split | leakage, fake accuracy (Ch 56.3) | a Pipeline |
| one random seed, one run | lucky results | several seeds, report the spread (Ch 62.1) |
ruff check --select ALL flags a large share of this table automatically;
start from it and turn off the rules you disagree with, deliberately.