Reviewing Python code for common anti-patterns to avoid
Python Anti-Patterns Checklist

Use this skill when reviewing Python code for common anti-patterns to avoid. Use as a checklist when reviewing code, before finalizing implementations, or when debugging issues that might stem from known bad practices.
--- name: python-anti-patterns description: Use this skill when reviewing Python code for common anti-patterns to avoid. Use as a checklist when reviewing code, before finalizing implementations, or when debugging issues that might stem from known bad practices. --- # Python Anti-Patterns Checklist A reference checklist of common mistakes and anti-patterns in Python code. Review this before finalizing implementations to catch issues early. ## When to Use This Skill - Reviewing code before merge - Debugging mysterious issues - Teaching or learning Python best practices - Establishing team coding standards - Refactoring legacy code **Note:** This skill focuses on what to avoid. For guidance on positive patterns and architecture, see the `python-design-patterns` skill.
Overview
A reference checklist of common mistakes and anti-patterns in Python code. Review this before finalizing implementations to catch issues early.
Manual install (npx, ZIP)
npx skills add wshobson/agents --skill python-anti-patterns -g -a claude-codemkdir -p ~/.claude/skills && curl -sL https://codeload.github.com/wshobson/agents/tar.gz/4236bb91f839 \
| tar -xz -C ~/.claude/skills --strip-components=4 agents-4236bb91f839/plugins/python-development/skills/python-anti-patternsNew-Item -ItemType Directory -Force "$HOME\.claude\skills" | Out-Null; Invoke-WebRequest "https://m.agentalley.io/zip/0caf2923b1dd4183fbdc/python-anti-patterns.zip" -OutFile "$env:TEMP\python-anti-patterns.zip"; Expand-Archive "$env:TEMP\python-anti-patterns.zip" -DestinationPath "$HOME\.claude\skills" -ForceConnects with Agent Alley MCP · agentalley.io/mcp
Files included
SKILL.md8.0 KB
Read the files here
wshobson/
Show the full text350 lines
Python Anti-Patterns Checklist
A reference checklist of common mistakes and anti-patterns in Python code. Review this before finalizing implementations to catch issues early.
When to Use This Skill
- Reviewing code before merge
- Debugging mysterious issues
- Teaching or learning Python best practices
- Establishing team coding standards
- Refactoring legacy code
Note: This skill focuses on what to avoid. For guidance on positive patterns and architecture, see the python-design-patterns skill.
Infrastructure Anti-Patterns
Scattered Timeout/Retry Logic
# BAD: Timeout logic duplicated everywhere
def fetch_user(user_id):
try:
return requests.get(url, timeout=30)
except Timeout:
logger.warning("Timeout fetching user")
return None
def fetch_orders(user_id):
try:
return requests.get(url, timeout=30)
except Timeout:
logger.warning("Timeout fetching orders")
return None
Fix: Centralize in decorators or client wrappers.
# GOOD: Centralized retry logic
@retry(stop=stop_after_attempt(3), wait=wait_exponential())
def http_get(url: str) -> Response:
return requests.get(url, timeout=30)
Double Retry
# BAD: Retrying at multiple layers
@retry(max_attempts=3) # Application retry
def call_service():
return client.request() # Client also has retry configured!
Fix: Retry at one layer only. Know your infrastructure's retry behavior.
Hard-Coded Configuration
# BAD: Secrets and config in code
DB_HOST = "prod-db.example.com"
API_KEY = "sk-12345"
def connect():
return psycopg.connect(f"host={DB_HOST}...")
Fix: Use environment variables with typed settings.
# GOOD
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
db_host: str = Field(alias="DB_HOST")
api_key: str = Field(alias="API_KEY")
settings = Settings()
Architecture Anti-Patterns
Exposed Internal Types
# BAD: Leaking ORM model to API
@app.get("/users/{id}")
def get_user(id: str) -> UserModel: # SQLAlchemy model
return db.query(UserModel).get(id)
Fix: Use DTOs/response models.
# GOOD
@app.get("/users/{id}")
def get_user(id: str) -> UserResponse:
user = db.query(UserModel).get(id)
return UserResponse.from_orm(user)
Mixed I/O and Business Logic
# BAD: SQL embedded in business logic
def calculate_discount(user_id: str) -> float:
user = db.query("SELECT * FROM users WHERE id = ?", user_id)
orders = db.query("SELECT * FROM orders WHERE user_id = ?", user_id)
# Business logic mixed with data access
if len(orders) > 10:
return 0.15
return 0.0
Fix: Repository pattern. Keep business logic pure.
# GOOD
def calculate_discount(user: User, orders: list[Order]) -> float:
# Pure business logic, easily testable
if len(orders) > 10:
return 0.15
return 0.0
Error Handling Anti-Patterns
Bare Exception Handling
# BAD: Swallowing all exceptions
try:
process()
except Exception:
pass # Silent failure - bugs hidden forever
Fix: Catch specific exceptions. Log or handle appropriately.
# GOOD
try:
process()
except ConnectionError as e:
logger.warning("Connection failed, will retry", error=str(e))
raise
except ValueError as e:
logger.error("Invalid input", error=str(e))
raise BadRequestError(str(e))
Ignored Partial Failures
# BAD: Stops on first error
def process_batch(items):
results = []
for item in items:
result = process(item) # Raises on error - batch aborted
results.append(result)
return results
Fix: Capture both successes and failures.
# GOOD
def process_batch(items) -> BatchResult:
succeeded = {}
failed = {}
for idx, item in enumerate(items):
try:
succeeded[idx] = process(item)
except Exception as e:
failed[idx] = e
return BatchResult(succeeded, failed)
Missing Input Validation
# BAD: No validation
def create_user(data: dict):
return User(**data) # Crashes deep in code on bad input
Fix: Validate early at API boundaries.
# GOOD
def create_user(data: dict) -> User:
validated = CreateUserInput.model_validate(data)
return User.from_input(validated)
Resource Anti-Patterns
Unclosed Resources
# BAD: File never closed
def read_file(path):
f = open(path)
return f.read() # What if this raises?
Fix: Use context managers.
# GOOD
def read_file(path):
with open(path) as f:
return f.read()
Blocking in Async
# BAD: Blocks the entire event loop
async def fetch_data():
time.sleep(1) # Blocks everything!
response = requests.get(url) # Also blocks!
Fix: Use async-native libraries.
# GOOD
async def fetch_data():
await asyncio.sleep(1)
async with httpx.AsyncClient() as client:
response = await client.get(url)
Type Safety Anti-Patterns
Missing Type Hints
# BAD: No types
def process(data):
return data["value"] * 2
Fix: Annotate all public functions.
# GOOD
def process(data: dict[str, int]) -> int:
return data["value"] * 2
Untyped Collections
# BAD: Generic list without type parameter
def get_users() -> list:
...
Fix: Use type parameters.
# GOOD
def get_users() -> list[User]:
...
Testing Anti-Patterns
Only Testing Happy Paths
# BAD: Only tests success case
def test_create_user():
user = service.create_user(valid_data)
assert user.id is not None
Fix: Test error conditions and edge cases.
# GOOD
def test_create_user_success():
user = service.create_user(valid_data)
assert user.id is not None
def test_create_user_invalid_email():
with pytest.raises(ValueError, match="Invalid email"):
service.create_user(invalid_email_data)
def test_create_user_duplicate_email():
service.create_user(valid_data)
with pytest.raises(ConflictError):
service.create_user(valid_data)
Over-Mocking
# BAD: Mocking everything
def test_user_service():
mock_repo = Mock()
mock_cache = Mock()
mock_logger = Mock()
mock_metrics = Mock()
# Test doesn't verify real behavior
Fix: Use integration tests for critical paths. Mock only external services.
Quick Review Checklist
Before finalizing code, verify:
- No scattered timeout/retry logic (centralized)
- No double retry (app + infrastructure)
- No hard-coded configuration or secrets
- No exposed internal types (ORM models, protobufs)
- No mixed I/O and business logic
- No bare
except Exception: pass - No ignored partial failures in batches
- No missing input validation
- No unclosed resources (using context managers)
- No blocking calls in async code
- All public functions have type hints
- Collections have type parameters
- Error paths are tested
- Edge cases are covered
Common Fixes Summary
| Anti-Pattern | Fix |
|---|---|
| Scattered retry logic | Centralized decorators |
| Hard-coded config | Environment variables + pydantic-settings |
| Exposed ORM models | DTO/response schemas |
| Mixed I/O + logic | Repository pattern |
| Bare except | Catch specific exceptions |
| Batch stops on error | Return BatchResult with successes/failures |
| No validation | Validate at boundaries with Pydantic |
| Unclosed resources | Context managers |
| Blocking in async | Async-native libraries |
| Missing types | Type annotations on all public APIs |
| Only happy path tests | Test errors and edge cases |
| 1 | |
| 2 | name python-anti-patterns |
| 3 | description Use this skill when reviewing Python code for common anti-patterns to avoid. Use as a checklist when reviewing code, before finalizing implementations, or when debugging issues that might stem from known bad practices. |
| 4 | |
| 5 | |
| 6 | # Python Anti-Patterns Checklist |
| 7 | |
| 8 | A reference checklist of common mistakes and anti-patterns in Python code. Review this before finalizing implementations to catch issues early. |
| 9 | |
| 10 | ## When to Use This Skill |
| 11 | |
| 12 | Reviewing code before merge |
| 13 | Debugging mysterious issues |
| 14 | Teaching or learning Python best practices |
| 15 | Establishing team coding standards |
| 16 | Refactoring legacy code |
| 17 | |
| 18 | **Note:** This skill focuses on what to avoid. For guidance on positive patterns and architecture, see the `python-design-patterns` skill. |
| 19 | |
| 20 | ## Infrastructure Anti-Patterns |
| 21 | |
| 22 | ### Scattered Timeout/Retry Logic |
| 23 | |
| 24 | |
| 25 | # BAD: Timeout logic duplicated everywhere |
| 26 | def fetch_user(user_id): |
| 27 | try: |
| 28 | return requests.get(url, timeout=30) |
| 29 | except Timeout: |
| 30 | logger.warning("Timeout fetching user") |
| 31 | return None |
| 32 | |
| 33 | def fetch_orders(user_id): |
| 34 | try: |
| 35 | return requests.get(url, timeout=30) |
| 36 | except Timeout: |
| 37 | logger.warning("Timeout fetching orders") |
| 38 | return None |
| 39 | |
| 40 | |
| 41 | **Fix:** Centralize in decorators or client wrappers. |
| 42 | |
| 43 | |
| 44 | # GOOD: Centralized retry logic |
| 45 | @retry(stop=stop_after_attempt(3), wait=wait_exponential()) |
| 46 | def http_get(url: str) -> Response: |
| 47 | return requests.get(url, timeout=30) |
| 48 | |
| 49 | |
| 50 | ### Double Retry |
| 51 | |
| 52 | |
| 53 | # BAD: Retrying at multiple layers |
| 54 | @retry(max_attempts=3) # Application retry |
| 55 | def call_service(): |
| 56 | return client.request() # Client also has retry configured! |
| 57 | |
| 58 | |
| 59 | **Fix:** Retry at one layer only. Know your infrastructure's retry behavior. |
| 60 | |
| 61 | ### Hard-Coded Configuration |
| 62 | |
| 63 | |
| 64 | # BAD: Secrets and config in code |
| 65 | DB_HOST = "prod-db.example.com" |
| 66 | API_KEY = "sk-12345" |
| 67 | |
| 68 | def connect(): |
| 69 | return psycopg.connect(f"host={DB_HOST}...") |
| 70 | |
| 71 | |
| 72 | **Fix:** Use environment variables with typed settings. |
| 73 | |
| 74 | |
| 75 | # GOOD |
| 76 | from pydantic_settings import BaseSettings |
| 77 | |
| 78 | class Settings(BaseSettings): |
| 79 | db_host: str = Field(alias="DB_HOST") |
| 80 | api_key: str = Field(alias="API_KEY") |
| 81 | |
| 82 | settings = Settings() |
| 83 | |
| 84 | |
| 85 | ## Architecture Anti-Patterns |
| 86 | |
| 87 | ### Exposed Internal Types |
| 88 | |
| 89 | |
| 90 | # BAD: Leaking ORM model to API |
| 91 | @app.get("/users/{id}") |
| 92 | def get_user(id: str) -> UserModel: # SQLAlchemy model |
| 93 | return db.query(UserModel).get(id) |
| 94 | |
| 95 | |
| 96 | **Fix:** Use DTOs/response models. |
| 97 | |
| 98 | |
| 99 | # GOOD |
| 100 | @app.get("/users/{id}") |
| 101 | def get_user(id: str) -> UserResponse: |
| 102 | user = db.query(UserModel).get(id) |
| 103 | return UserResponse.from_orm(user) |
| 104 | |
| 105 | |
| 106 | ### Mixed I/O and Business Logic |
| 107 | |
| 108 | |
| 109 | # BAD: SQL embedded in business logic |
| 110 | def calculate_discount(user_id: str) -> float: |
| 111 | user = db.query("SELECT * FROM users WHERE id = ?", user_id) |
| 112 | orders = db.query("SELECT * FROM orders WHERE user_id = ?", user_id) |
| 113 | # Business logic mixed with data access |
| 114 | if len(orders) > 10: |
| 115 | return 0.15 |
| 116 | return 0.0 |
| 117 | |
| 118 | |
| 119 | **Fix:** Repository pattern. Keep business logic pure. |
| 120 | |
| 121 | |
| 122 | # GOOD |
| 123 | def calculate_discount(user: User, orders: list[Order]) -> float: |
| 124 | # Pure business logic, easily testable |
| 125 | if len(orders) > 10: |
| 126 | return 0.15 |
| 127 | return 0.0 |
| 128 | |
| 129 | |
| 130 | ## Error Handling Anti-Patterns |
| 131 | |
| 132 | ### Bare Exception Handling |
| 133 | |
| 134 | |
| 135 | # BAD: Swallowing all exceptions |
| 136 | try: |
| 137 | process() |
| 138 | except Exception: |
| 139 | pass # Silent failure - bugs hidden forever |
| 140 | |
| 141 | |
| 142 | **Fix:** Catch specific exceptions. Log or handle appropriately. |
| 143 | |
| 144 | |
| 145 | # GOOD |
| 146 | try: |
| 147 | process() |
| 148 | except ConnectionError as e: |
| 149 | logger.warning("Connection failed, will retry", error=str(e)) |
| 150 | raise |
| 151 | except ValueError as e: |
| 152 | logger.error("Invalid input", error=str(e)) |
| 153 | raise BadRequestError(str(e)) |
| 154 | |
| 155 | |
| 156 | ### Ignored Partial Failures |
| 157 | |
| 158 | |
| 159 | # BAD: Stops on first error |
| 160 | def process_batch(items): |
| 161 | results = [] |
| 162 | for item in items: |
| 163 | result = process(item) # Raises on error - batch aborted |
| 164 | results.append(result) |
| 165 | return results |
| 166 | |
| 167 | |
| 168 | **Fix:** Capture both successes and failures. |
| 169 | |
| 170 | |
| 171 | # GOOD |
| 172 | def process_batch(items) -> BatchResult: |
| 173 | succeeded = {} |
| 174 | failed = {} |
| 175 | for idx, item in enumerate(items): |
| 176 | try: |
| 177 | succeeded[idx] = process(item) |
| 178 | except Exception as e: |
| 179 | failed[idx] = e |
| 180 | return BatchResult(succeeded, failed) |
| 181 | |
| 182 | |
| 183 | ### Missing Input Validation |
| 184 | |
| 185 | |
| 186 | # BAD: No validation |
| 187 | def create_user(data: dict): |
| 188 | return User(**data) # Crashes deep in code on bad input |
| 189 | |
| 190 | |
| 191 | **Fix:** Validate early at API boundaries. |
| 192 | |
| 193 | |
| 194 | # GOOD |
| 195 | def create_user(data: dict) -> User: |
| 196 | validated = CreateUserInput.model_validate(data) |
| 197 | return User.from_input(validated) |
| 198 | |
| 199 | |
| 200 | ## Resource Anti-Patterns |
| 201 | |
| 202 | ### Unclosed Resources |
| 203 | |
| 204 | |
| 205 | # BAD: File never closed |
| 206 | def read_file(path): |
| 207 | f = open(path) |
| 208 | return f.read() # What if this raises? |
| 209 | |
| 210 | |
| 211 | **Fix:** Use context managers. |
| 212 | |
| 213 | |
| 214 | # GOOD |
| 215 | def read_file(path): |
| 216 | with open(path) as f: |
| 217 | return f.read() |
| 218 | |
| 219 | |
| 220 | ### Blocking in Async |
| 221 | |
| 222 | |
| 223 | # BAD: Blocks the entire event loop |
| 224 | async def fetch_data(): |
| 225 | time.sleep(1) # Blocks everything! |
| 226 | response = requests.get(url) # Also blocks! |
| 227 | |
| 228 | |
| 229 | **Fix:** Use async-native libraries. |
| 230 | |
| 231 | |
| 232 | # GOOD |
| 233 | async def fetch_data(): |
| 234 | await asyncio.sleep(1) |
| 235 | async with httpx.AsyncClient() as client: |
| 236 | response = await client.get(url) |
| 237 | |
| 238 | |
| 239 | ## Type Safety Anti-Patterns |
| 240 | |
| 241 | ### Missing Type Hints |
| 242 | |
| 243 | |
| 244 | # BAD: No types |
| 245 | def process(data): |
| 246 | return data["value"] * 2 |
| 247 | |
| 248 | |
| 249 | **Fix:** Annotate all public functions. |
| 250 | |
| 251 | |
| 252 | # GOOD |
| 253 | def process(data: dict[str, int]) -> int: |
| 254 | return data["value"] * 2 |
| 255 | |
| 256 | |
| 257 | ### Untyped Collections |
| 258 | |
| 259 | |
| 260 | # BAD: Generic list without type parameter |
| 261 | def get_users() -> list: |
| 262 | ... |
| 263 | |
| 264 | |
| 265 | **Fix:** Use type parameters. |
| 266 | |
| 267 | |
| 268 | # GOOD |
| 269 | def get_users() -> list[User]: |
| 270 | ... |
| 271 | |
| 272 | |
| 273 | ## Testing Anti-Patterns |
| 274 | |
| 275 | ### Only Testing Happy Paths |
| 276 | |
| 277 | |
| 278 | # BAD: Only tests success case |
| 279 | def test_create_user(): |
| 280 | user = service.create_user(valid_data) |
| 281 | assert user.id is not None |
| 282 | |
| 283 | |
| 284 | **Fix:** Test error conditions and edge cases. |
| 285 | |
| 286 | |
| 287 | # GOOD |
| 288 | def test_create_user_success(): |
| 289 | user = service.create_user(valid_data) |
| 290 | assert user.id is not None |
| 291 | |
| 292 | def test_create_user_invalid_email(): |
| 293 | with pytest.raises(ValueError, match="Invalid email"): |
| 294 | service.create_user(invalid_email_data) |
| 295 | |
| 296 | def test_create_user_duplicate_email(): |
| 297 | service.create_user(valid_data) |
| 298 | with pytest.raises(ConflictError): |
| 299 | service.create_user(valid_data) |
| 300 | |
| 301 | |
| 302 | ### Over-Mocking |
| 303 | |
| 304 | |
| 305 | # BAD: Mocking everything |
| 306 | def test_user_service(): |
| 307 | mock_repo = Mock() |
| 308 | mock_cache = Mock() |
| 309 | mock_logger = Mock() |
| 310 | mock_metrics = Mock() |
| 311 | # Test doesn't verify real behavior |
| 312 | |
| 313 | |
| 314 | **Fix:** Use integration tests for critical paths. Mock only external services. |
| 315 | |
| 316 | ## Quick Review Checklist |
| 317 | |
| 318 | Before finalizing code, verify: |
| 319 | |
| 320 | [ ] No scattered timeout/retry logic (centralized) |
| 321 | [ ] No double retry (app + infrastructure) |
| 322 | [ ] No hard-coded configuration or secrets |
| 323 | [ ] No exposed internal types (ORM models, protobufs) |
| 324 | [ ] No mixed I/O and business logic |
| 325 | [ ] No bare `except Exception: pass` |
| 326 | [ ] No ignored partial failures in batches |
| 327 | [ ] No missing input validation |
| 328 | [ ] No unclosed resources (using context managers) |
| 329 | [ ] No blocking calls in async code |
| 330 | [ ] All public functions have type hints |
| 331 | [ ] Collections have type parameters |
| 332 | [ ] Error paths are tested |
| 333 | [ ] Edge cases are covered |
| 334 | |
| 335 | ## Common Fixes Summary |
| 336 | |
| 337 | | Anti-Pattern | Fix | |
| 338 | |-------------|-----| |
| 339 | | Scattered retry logic | Centralized decorators | |
| 340 | | Hard-coded config | Environment variables + pydantic-settings | |
| 341 | | Exposed ORM models | DTO/response schemas | |
| 342 | | Mixed I/O + logic | Repository pattern | |
| 343 | | Bare except | Catch specific exceptions | |
| 344 | | Batch stops on error | Return BatchResult with successes/failures | |
| 345 | | No validation | Validate at boundaries with Pydantic | |
| 346 | | Unclosed resources | Context managers | |
| 347 | | Blocking in async | Async-native libraries | |
| 348 | | Missing types | Type annotations on all public APIs | |
| 349 | | Only happy path tests | Test errors and edge cases | |
| 350 |
Details
- Updated
- Sep 21, 2026
- License
- MIT
- Stars on the repo
- ★ 39,857
- Checked
- Source
- wshobson/agents ↗
- Creator
- wshobson · 179 listings
Similar skills
See all →
SkillExtract text, tables, metadataXberg Document Extraction
SkillMove code from one implementation to another functionCode migration
SkillManages Ahrefs API usage in Python using ahrefs-python
MCPDocx LLM translatoradeu
SkillCreate an Amazon-compliant white-background main imageAmazon White-Background Main Image · 亚马逊白底主图
MCPOfficial monorepo of SDKs, CLISendmux sdkMore from wshobson
SkillMaster C/.NET backend development patterns for building
SkillImplement WCAG 2.2 compliant interfaces with mobileAccessibility compliance
SkillMaster Material Design 3 and Jetpack Compose patternsAndroid Mobile Design
SkillMigrate from AngularJS to Angular using hybrid modeAngular migration
SkillUnderstand anti-reversing, obfuscationAnti reversing techniques
SkillBuild production Apache Airflow DAGs with best practicesApache Airflow DAG Patterns