316 lines
6.2 KiB
Markdown
316 lines
6.2 KiB
Markdown
---
|
|
name: python
|
|
description: "Language-specific super-code guidelines for python."
|
|
risk: safe
|
|
source: community
|
|
date_added: "2026-06-16"
|
|
---
|
|
# Python: Idiomatic Efficiency Reference
|
|
|
|
## Table of Contents
|
|
1. [Comprehensions & Generators](#comprehensions)
|
|
2. [Unpacking & Destructuring](#unpacking)
|
|
3. [Built-ins & stdlib](#builtins)
|
|
4. [Functions & Defaults](#functions)
|
|
5. [Classes & Dataclasses](#classes)
|
|
6. [Error Handling](#errors)
|
|
7. [Type Hints](#types)
|
|
8. [Anti-patterns specific to Python](#antipatterns)
|
|
|
|
---
|
|
|
|
## 1. Comprehensions & Generators {#comprehensions}
|
|
|
|
```python
|
|
# ❌ Imperative accumulation
|
|
result = []
|
|
for item in items:
|
|
if item.active:
|
|
result.append(item.name.upper())
|
|
|
|
# ✅
|
|
result = [item.name.upper() for item in items if item.active]
|
|
```
|
|
|
|
```python
|
|
# ❌ Dict built in a loop
|
|
d = {}
|
|
for k, v in pairs:
|
|
d[k] = v
|
|
|
|
# ✅
|
|
d = dict(pairs)
|
|
# or
|
|
d = {k: v for k, v in pairs}
|
|
```
|
|
|
|
```python
|
|
# ❌ Generator converted to list unnecessarily
|
|
total = sum(list(x * 2 for x in nums))
|
|
|
|
# ✅ — generator expression works directly in sum()
|
|
total = sum(x * 2 for x in nums)
|
|
```
|
|
|
|
**Use generator expressions (not list comprehensions) when the result is consumed once and not stored.**
|
|
|
|
---
|
|
|
|
## 2. Unpacking & Destructuring {#unpacking}
|
|
|
|
```python
|
|
# ❌ Index access
|
|
first = items[0]
|
|
rest = items[1:]
|
|
|
|
# ✅
|
|
first, *rest = items
|
|
```
|
|
|
|
```python
|
|
# ❌ Temporary variable for swap
|
|
tmp = a
|
|
a = b
|
|
b = tmp
|
|
|
|
# ✅
|
|
a, b = b, a
|
|
```
|
|
|
|
```python
|
|
# ❌ items() with separate indexing
|
|
for i in range(len(items)):
|
|
print(i, items[i])
|
|
|
|
# ✅
|
|
for i, item in enumerate(items):
|
|
print(i, item)
|
|
```
|
|
|
|
```python
|
|
# ❌ zip with separate index
|
|
for i in range(len(a)):
|
|
process(a[i], b[i])
|
|
|
|
# ✅
|
|
for x, y in zip(a, b):
|
|
process(x, y)
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Built-ins & stdlib {#builtins}
|
|
|
|
```python
|
|
# ❌ Manual max search
|
|
max_val = items[0]
|
|
for item in items[1:]:
|
|
if item > max_val:
|
|
max_val = item
|
|
|
|
# ✅
|
|
max_val = max(items)
|
|
```
|
|
|
|
```python
|
|
# ❌ Manual grouping
|
|
from collections import defaultdict
|
|
groups = defaultdict(list)
|
|
for item in items:
|
|
groups[item.category].append(item)
|
|
|
|
# ✅ — same thing, just be explicit about defaultdict; it IS the right tool
|
|
# (this example is already correct — don't replace defaultdict with a loop)
|
|
```
|
|
|
|
```python
|
|
# ❌ Manual sentinel for dict default
|
|
if key in d:
|
|
val = d[key]
|
|
else:
|
|
val = default
|
|
|
|
# ✅
|
|
val = d.get(key, default)
|
|
```
|
|
|
|
```python
|
|
# ❌ Rolling your own counter
|
|
counts = {}
|
|
for item in items:
|
|
counts[item] = counts.get(item, 0) + 1
|
|
|
|
# ✅
|
|
from collections import Counter
|
|
counts = Counter(items)
|
|
```
|
|
|
|
**Use `itertools` (chain, islice, groupby, product) before writing nested loops for combinatorial or streaming logic.**
|
|
|
|
---
|
|
|
|
## 4. Functions & Defaults {#functions}
|
|
|
|
```python
|
|
# ❌ Mutable default argument (bug, not just style)
|
|
def append_to(item, lst=[]):
|
|
lst.append(item)
|
|
return lst
|
|
|
|
# ✅
|
|
def append_to(item, lst=None):
|
|
if lst is None:
|
|
lst = []
|
|
lst.append(item)
|
|
return lst
|
|
```
|
|
|
|
```python
|
|
# ❌ Positional args for everything when keyword clarity helps
|
|
create_user("Alice", True, False, 30)
|
|
|
|
# ✅ — use keyword args at call site for boolean/ambiguous params
|
|
create_user("Alice", is_admin=True, is_active=False, age=30)
|
|
```
|
|
|
|
```python
|
|
# ❌ Long function doing multiple things
|
|
def process_and_save(data):
|
|
# 40 lines of transform
|
|
# 20 lines of DB write
|
|
...
|
|
|
|
# ✅ — split only if each part is reused OR independently testable
|
|
def _transform(data): ...
|
|
def _save(record): ...
|
|
def process_and_save(data): _save(_transform(data))
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Classes & Dataclasses {#classes}
|
|
|
|
```python
|
|
# ❌ Manual __init__ for data holders
|
|
class Point:
|
|
def __init__(self, x, y):
|
|
self.x = x
|
|
self.y = y
|
|
|
|
# ✅
|
|
from dataclasses import dataclass
|
|
|
|
@dataclass
|
|
class Point:
|
|
x: float
|
|
y: float
|
|
```
|
|
|
|
```python
|
|
# ❌ Class just to hold a namespace of functions
|
|
class MathUtils:
|
|
@staticmethod
|
|
def add(a, b): return a + b
|
|
|
|
# ✅ — module-level functions; classes for state + behavior
|
|
def add(a, b): return a + b
|
|
```
|
|
|
|
```python
|
|
# ❌ __repr__ written manually when dataclass gives it free
|
|
# (see above — use @dataclass)
|
|
```
|
|
|
|
**Use `@dataclass(frozen=True)` for immutable value objects. Use `NamedTuple` when you need tuple unpacking.**
|
|
|
|
---
|
|
|
|
## 6. Error Handling {#errors}
|
|
|
|
```python
|
|
# ❌ Bare except
|
|
try:
|
|
risky()
|
|
except:
|
|
pass
|
|
|
|
# ✅ — catch the specific exception; don't swallow silently
|
|
try:
|
|
risky()
|
|
except ValueError as e:
|
|
logger.warning("Invalid value: %s", e)
|
|
```
|
|
|
|
```python
|
|
# ❌ LBYL (look before you leap) when EAFP is cleaner
|
|
if os.path.exists(path):
|
|
with open(path) as f:
|
|
data = f.read()
|
|
|
|
# ✅ (EAFP)
|
|
try:
|
|
with open(path) as f:
|
|
data = f.read()
|
|
except FileNotFoundError:
|
|
data = None
|
|
```
|
|
|
|
```python
|
|
# ❌ Re-raising with raise e (loses traceback)
|
|
except Exception as e:
|
|
raise e
|
|
|
|
# ✅
|
|
except Exception:
|
|
raise # bare raise preserves original traceback
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Type Hints {#types}
|
|
|
|
```python
|
|
# ❌ Overly verbose Union syntax (Python <3.10 style in new code)
|
|
from typing import Optional, Union
|
|
def f(x: Optional[int]) -> Union[str, None]: ...
|
|
|
|
# ✅ (Python 3.10+)
|
|
def f(x: int | None) -> str | None: ...
|
|
```
|
|
|
|
```python
|
|
# ❌ Any where a TypeVar or Protocol would be informative
|
|
from typing import Any
|
|
def first(lst: list[Any]) -> Any: ...
|
|
|
|
# ✅
|
|
from typing import TypeVar
|
|
T = TypeVar("T")
|
|
def first(lst: list[T]) -> T: ...
|
|
```
|
|
|
|
**Don't add type hints to every local variable — annotate function signatures and class fields; leave obvious locals inferred.**
|
|
|
|
---
|
|
|
|
## 8. Anti-patterns specific to Python {#antipatterns}
|
|
|
|
| Anti-pattern | Preferred |
|
|
|---|---|
|
|
| `len(lst) == 0` | `not lst` |
|
|
| `if x == True:` | `if x:` |
|
|
| `if x == None:` | `if x is None:` |
|
|
| `range(len(lst))` for iteration | `enumerate(lst)` |
|
|
| String concatenation in a loop | `"".join(parts)` |
|
|
| `import *` | explicit imports |
|
|
| Catching `Exception` to log and re-raise | bare `raise` or let it propagate |
|
|
| `print()` for debug output | `logging.debug()` |
|
|
| `os.path.join` (Python 3.4+) | `pathlib.Path / "subpath"` |
|
|
| Manual `__eq__` + `__hash__` on value objects | `@dataclass(eq=True, frozen=True)` |
|
|
|
|
|
|
|
|
## Limitations
|
|
- These are language-specific guidelines and do not cover overall architectural decisions.
|
|
- Over-compression might reduce readability; apply judgement.
|