Python Tricks I Actually Reach For Daily

Table of Contents
Tuple Unpacking
Tuple Unpacking
enumerate()
enumerate()
zip()
zip()
dict.get()
dict.get()
Set lookup
Set lookup
Walrus operator
Walrus operator
dataclass
dataclass
pathlib.Path
pathlib.Path
Forget the clever golfed code that reads like riddles. These are the Python features I reach for in production code—the ones that reduce boilerplate, prevent actual bugs, and make other developers nod in approval rather than scrolling to Stack Overflow.
The Core 7 (with real context)
1. Tuple Unpacking for Swaps
No temp variable, no ceremony:
a, b = b, a
This creates a tuple on the right, then destructures it on the left. Works in-place, zero overhead. The same pattern handles multi-values:
# Swap three values in one line
i, j, k = j, k, i
# Unpack a function's multiple return values
x_coord, y_coord, label = get_point()
# Swap dict entries
d = {'a': 1, 'b': 2}
d['a'], d['b'] = d['b'], d['a'] # Now {'a': 2, 'b': 1}
2. enumerate() — Index + Value
Stop doing for i in range(len(items)). It's error-prone and reads backwards:
# ❌ Old way — off-by-one bugs hiding here
for i in range(len(colors)):
print(i, colors[i])
# ✅ Python way — what you meant
for i, color in enumerate(colors):
print(i, color)
Pro move: start at 1
enumerate(colors, start=1) gives 1-based numbering—useful for display, error messages, test cases.
3. zip() — Loop Two Sequences in Parallel
Cleaner than manual index management when two sequences must stay in sync:
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]
subjects = ["Math", "Science", "English"]
# Pairing two lists
for name, score in zip(names, scores):
print(f"{name}: {score}")
# Three-way zipping (tuples auto-unpacked)
for name, score, subject in zip(names, scores, subjects):
print(f"{name} ({subject}): {score}")
# Dictionaries from two lists
scores_dict = dict(zip(names, scores))
# {'Alice': 85, 'Bob': 92, 'Charlie': 78}
zip truncates to the shortest
If names has 3 items and scores has 5, only 3 pairs are produced. Use itertools.zip_longest if you want padding behavior.
4. dict.get() — Avoid KeyError
The difference between "missing key expected" and "missing key is a bug":
config = {"port": 3000, "host": "localhost"}
# Returns default if key missing — no exception
port = config.get("port", 8080) # 3000
debug = config.get("debug", False) # False (key missing)
host = config.get("host", "0.0.0.0") # "localhost"
# None is the implicit default
value = config.get("nonexistent") # None
| Scenario | Use | Reason |
|---|---|---|
| Required config, missing = bug | <code>config["key"]</code> | Fail fast, surface error |
| Optional key, default OK | <code>config.get("key", default)</code> | Graceful fallback |
| Need to distinguish missing from None | <code>if "key" in config</code> | None is a valid value |
5. Set Lookups for O(1) Membership
Converting a list to a set before checking membership is one of the most common "hidden slowness" fixes:
allowed_actions = {"read", "write", "delete", "update"}
# O(1) — constant time regardless of list size
if action in allowed_actions:
execute(action)
# ❌ With a list, this is O(n) — scales poorly
allowed_actions_list = ["read", "write", ...]
if action in allowed_actions_list: # Slower as list grows
execute(action)
Rule of thumb
If x in collection runs more than a handful of times per request and the collection has more than ~10 items, it should almost certainly be a set.
6. Underscore `_` for Intentional Ignoring
Communicates "I'm not using this value on purpose":
# Unpacking — ignore the key, keep the value
_, result = parse_line(line)
# Loop — ignore the index
for _ in range(3):
print("repeat")
# Match with explicit wildcard (Python 3.10+)
match status:
case 200:
handle_ok()
case 404:
handle_missing()
case _:
handle_other() # "anything else"
# Unpacking partial — keep start/end, ignore middle
first, *_, last = [1, 2, 3, 4, 5]
# first=1, last=5
Deep-Cut Techniques (Actually Useful)
Assign and test in a single expression—eliminates calling the same function twice:
<Alert type="note" title="When NOT to use walrus">
Don't force it into existing clean code. If the assignment belongs outside the condition, put it there. The walrus shines in: loop guards, catch blocks, and reducing duplicated function calls.
</Alert>
<Quiz
question="When is the walrus operator `:=` most valuable?"
options={[
"In simple variable assignments like `x := 5`",
"In loop guards and reducing duplicated function calls",
"To replace all `=` assignments",
"When you want to make code shorter no matter what"
]}
correctIndex={1}
explanation="The walrus shines in loop guards (`while (line := f.readline())`), catch blocks (`except Exception as e`), and avoiding duplicate calls (`if (n := expensive()) > 10`). Don't force it where simple assignment is clearer."
/>
</AccordionItem>
<AccordionItem title="8. f-strings from Python 3.12+">
Python 3.12 added f-string debugging with <code>=</code> and multiline f-strings:
```python filename="fstrings.py"
name, age = "Alice", 30
# Debug mode — shows expression + value
print(f"{name=} {age=}") # name='Alice' age=30
# Multiline (no more backslash continuation)
report = f"""
Name: {name}
Status: {"Adult" if age >= 18 else "Minor"}
Score: {95.5:.1f}%
"""
Stop concatenating with os.path.join():
from pathlib import Path
base = Path("/app/data")
# Fluent chaining — works cross-platform
config = base / "config" / f"env_{ENV}.yaml"
logs = base / "logs" / "app.log"
# Read/write without manual open()
text = config.read_text(encoding="utf-8")
config.write_text(new_text, encoding="utf-8")
# Existence checks
if config.exists() and config.is_file():
size = config.stat().st_size
# Iterate directory
for log_file in base.glob("**/*.log"):
print(log_file.name, log_file.stat().st_size)
Why pathlib over os.path?
Path uses operator overloading (/ joins paths) which reads naturally. Methods are chainable. The same code works on Windows and Linux without changes.
Before dataclasses, classes with many attributes needed verbose init, repr, and eq. Dataclasses generate those automatically:
from dataclasses import dataclass, field
from typing import List
@dataclass
class Project:
name: str
slug: str
tags: List[str] = field(default_factory=list)
starred: bool = False
# ✅ Auto-generated __init__, __repr__, __eq__
p = Project(
name="Portfolio",
slug="portfolio",
tags=["nextjs", "tailwind"],
starred=True,
)
print(p) # Project(name='Portfolio', slug='portfolio', ...)
# ✅ Frozen dataclass = immutable (no keyword mutability issues)
from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: float
y: float
pt = Point(1.0, 2.0)
# pt.x = 3.0 # FrozenInstanceError
| Feature | Regular Class | Dataclass |
|---|---|---|
| __init__ | Manual boilerplate | Auto-generated |
| __repr__ | Manual or ugly default | Clean: Class(field=value, ...) |
| __eq__ | Identity only (unless manual) | Value equality |
| Immutability | Manual property setters | @dataclass(frozen=True) |
Combine defaultdict with context managers for clean resource handling:
from collections import defaultdict
from contextlib import contextmanager
from threading import Lock
# Grouping — defaultdict eliminates "if key in dict" checks
grouped = defaultdict(list)
for item in items:
grouped[item.category].append(item)
# Custom context manager for timing
@contextmanager
def timer(label):
import time
start = time.perf_counter()
yield
elapsed = time.perf_counter() - start
print(f"{label}: {elapsed*1000:.2f}ms")
with timer("database query"):
results = db.query("SELECT * FROM users")
Decision Reference
| Problem | Solution | Why |
|---|---|---|
| Avoid KeyError on optional config | <code>dict.get(key, default)</code> | Graceful fallback, no try/except |
| Loop with index + value | <code>enumerate(items)</code> | Eliminates range(len(...)) anti-pattern |
| Pair two sequences | <code>zip(a, b)</code> | No manual index state |
| Fast membership check | <code>set</code> | O(1) vs O(n) for lists |
| Assign + test in one line | <code>:=</code> walrus | Remove duplicate function calls |
| Group by key | <code>defaultdict(list)</code> | No if key not in dict boilerplate |
| Typed record with methods | <code>@dataclass</code> | Auto-init/eq/repr, less boilerplate |
| Cross-platform paths | <code>pathlib.Path</code> | Fluent API, chainable |
TL;DR
Start with these today
Minimum viable skill stack:- Use
enumerate()andzip()—they eliminate 80% of loop bugs - Use
dict.get()for optional configs—fail only where it's a real error - Use
_consistently—it signals intent to humans and linters - Profile before optimizing, but profile sets first—they're the fastest free win
Related Posts
- Python Fundamentals You Actually Need — Start here if you're new to Python
- Modern Python Development Environment — Set up pyenv, Poetry, uv, and pyproject.toml
- Python Practice: Automation Scripts — Real-world automation examples
You Might Also Like
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

Python's Underscores: Conventions, Not Access Modifiers
Python uses underscores as naming conventions, not enforced access modifiers—understanding the difference prevents confusing code and helps you write better APIs.
Read more
Unit Tests in JavaScript: Practical Patterns That Actually Prevent Production Bugs
Pragmatic guide to writing resilient JavaScript unit tests: the AAA pattern, test fixtures, property-based testing with fast-check, and boundary mocking.
Read more
A Practical pytest Tutorial That Actually Fixes Early Gotchas
Master pytest for production Python: fixtures, parameterization, monkeypatch mocking, custom markers, and clean test suite architecture.
Read more