Pythonic PatternsLesson 2 of 2
Modern Idioms
Python keeps adding small features that remove boilerplate. You will see them in any recent codebase, so it pays to recognize them.
The walrus operator := (3.8) assigns and returns a value inside an expression. It shines in while loops (while (line := f.readline()):) and in comprehensions where you would otherwise compute something twice: [s for r in rows if (s := r.strip())].
f-strings have a debugging shortcut: f"{total=}" prints total=42, with the expression text included. It is the console.log({ total }) trick, built into the language.
The * and ** operators also unpack at call sites: f(*args) spreads a list into positional arguments and f(**options) spreads a dict into keyword arguments, like fn(...args) in JS.
Dicts can be merged with | (3.9): merged = defaults | overrides, where the right side wins, like { ...defaults, ...overrides }. And d |= other updates in place.
Strings gained removeprefix() and removesuffix() (3.9). Unlike lstrip()/rstrip(), which remove any of the given CHARACTERS, these remove an exact substring once, which is almost always what you meant.
itertools.batched(iterable, n) (3.12) splits any iterable into tuples of n items, a chunk() helper you no longer need to write. And for file paths, prefer pathlib.Path over the older os.path functions.
Quick reference
| JavaScript | Python | Note |
|---|---|---|
| let m; while ((m = re.exec(s))) | while (m := pattern.search(s)): | Walrus := |
| console.log({ total }) | print(f"{total=}") | Prints total=42 |
| fn(...args) | fn(*args) | Spread into positional args |
| fn({ ...options }) | fn(**options) | Spread into keyword args |
| { ...defaults, ...overrides } | defaults | overrides | Dict merge (3.9+) |
| s.startsWith(p) ? s.slice(p.length) : s | s.removeprefix(p) | Exact prefix, once |
| lodash _.chunk(arr, 3) | itertools.batched(arr, 3) | Built in (3.12+) |
Side by side
// JavaScript
const cleaned = rows
.map(r => r.trim())
.filter(s => s.length > 0);
console.log({ total });
const settings = { ...defaults, ...overrides };
const name = file.endsWith(".csv")
? file.slice(0, -4)
: file;
// chunk() helper or lodash
const batches = _.chunk(items, 3);# Walrus: assign inside an expression
cleaned = [s for r in rows if (s := r.strip())]
# Self-documenting debug output
total = 42
print(f"{total=}") # total=42
# Unpacking at the call site
def connect(host, port, timeout=5): ...
args = ["localhost", 8080]
options = {"timeout": 10}
connect(*args, **options)
# Dict merge
settings = defaults | overrides
settings |= {"debug": True}
# Exact prefix/suffix removal
"report_2024.csv".removeprefix("report_") # '2024.csv'
"report_2024.csv".removesuffix(".csv") # 'report_2024'
# Batching (3.12+)
from itertools import batched
list(batched(range(7), 3)) # [(0, 1, 2), (3, 4, 5), (6,)]Exercise
Real Python, running in your browserUse modern idioms (readings, defaults, overrides and filenames are already defined):
readings = [" temp=21 ", "", "humidity=40", " ", "temp=23"]
defaults = {"unit": "C", "precision": 1}
overrides = {"precision": 2}
filenames = ["report_2024.csv", "report_2025.csv", "notes.txt"]
1. cleaned: the stripped, non-empty readings, using the walrus operator
in a comprehension
2. settings: defaults merged with overrides using |
3. names: each filename with the "report_" prefix and ".csv" suffix removed
(use removeprefix/removesuffix)
4. batches: list(batched(range(7), 3))
5. Print settings with the f"{settings=}" debug syntaxsettings={'unit': 'C', 'precision': 2}Ctrl + Enter runCtrl + Shift + Enter checkTab indentEsc then Tab to leave the editorCtrl + / comment