Skip to lesson
Python for JS Developers

Pythonic PatternsLesson 2 of 2

Modern Idioms

3 min

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

JavaScriptPythonNote
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 | overridesDict merge (3.9+)
s.startsWith(p) ? s.slice(p.length) : ss.removeprefix(p)Exact prefix, once
lodash _.chunk(arr, 3)itertools.batched(arr, 3)Built in (3.12+)

Side by side

JavaScript
// 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);
Python
# 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 browser

Use modern idioms (readings, defaults, overrides and filenames are already defined):

Starter code
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 syntax
Expected output
settings={'unit': 'C', 'precision': 2}

Ctrl + Enter runCtrl + Shift + Enter checkTab indentEsc then Tab to leave the editorCtrl + / comment