Faragiri

Learn Python basics Python

Eight idioms shown against the version a beginner writes first, including the f-string that prints its own expression and the assignment inside a condition.

4Sections
3Min read
4Depth

Same three rows, twice

The version that works, and the version that has nowhere to hide a bug

Both columns run and both give the same answer. The right hand column has no index in it, which is where the difference actually is.

Written from another languageIndexes, counters and a temporary list
Written in PythonUnpacking, and the loop states its own shape
Reading three fieldsr[0], r[1], r[2]for code, stock, price in rows
Wanting the positioni = 0, then i += 1enumerate(rows, start=1)
Pairing two listsA loop over range(len(codes))dict(zip(codes, prices))
Filtering into a dictAn empty dict and an if inside a loop{c: s for c, s, _ in rows if s}
A debug printprint("total", total)f"{total=}"

The right hand side is marked because on these five rows there is a right answer. That is not true of every pair in this language, and a figure that marks a winner is taking a position rather than comparing.

Python's syntax is small and its idioms are not, and the gap between working
Python and fluent Python is about eight of them. Take a stock file of three
rows, ("PL-14", 3, 240) and two like it, and the difference shows
up on every line.

  • Unpack in the loop header:
    for code, stock, price in rows rather than
    r[0], r[1], r[2]
  • enumerate(rows, start=1) when the position is wanted, which
    gives (1, 'PL-14') and no counter to maintain
  • dict(zip(codes, prices)) builds
    {'PL-14': 240, 'PL-15': 180, 'PL-16': 95} in one line
  • first, *rest = codes answers PL-14 and
    ['PL-15', 'PL-16']
  • codes[::-1] reverses without touching the original
  • A dict comprehension with a filter:
    {c: s for c, s, _ in rows if s} drops the out of stock row and
    answers {'PL-14': 3, 'PL-16': 7}

Two that are newer than most tutorials

An f-string ending in an equals sign prints the expression as well as its
value: f"{total=}" gives total=515. That is a debug
print that cannot drift from the name it is reporting, and it arrived in Python
3.8. Formatting joins on:
f"{total:,} toman across {len(rows)} lines" puts the thousands
separators in, and f"{total/len(rows):.1f} average" answers
171.7.

The walrus assigns inside a condition, so the value is computed once and
kept: if (comma := line.find(",")) != -1: then
line[:comma]. Without it the find runs twice or a name is bound on
the line above and lives longer than it should. It is worth exactly this much
and no more, which is why the rule is to use it only where the alternative is a
repeated call.

The sort key that carries two directions

Sorting by two things where one is descending does not need a comparison
function or two passes. Negate the numeric key:
sorted(rows, key=lambda r: (-r[1], r[0])) puts the most stock
first and breaks ties alphabetically, so PL-16 with 7 leads PL-14 with 3. The
tuple is compared left to right, which is the entire rule.

That trick only works on numbers. For a descending string key the answer is
two sorts in the opposite order, relying on stability, which is
the handful of ways, and
why your language already picked one
.

What to run in 2026

Ruff carries most of this mechanically, free and open source, and as of 2026
it is the default checker in Python projects. Its rules name the loop that
should have been a comprehension, the range(len(x)) that should
have been enumerate, and the manual index that should have been
unpacking. It replaced learning these one at a time from code review, which
works and takes about two years.

Python 3.14 is the release to be on, free, and its error messages are the
practical reason rather than any speed claim. It replaced Python 3.13, and for
the idioms above the version that matters is 3.8 for the equals f-string and the
walrus, which is old enough that anything still running without them is a
constraint worth naming rather than a preference.

Where this stalls people

The stall is arriving from another language and writing that language in
Python, which runs perfectly and gets called unpythonic in review without anyone
explaining the cost. The cost is real: for i in range(len(rows))
followed by rows[i][0] is three chances to get an index wrong on
every line, and the unpacked version has none.

The check that shortens the two years: any time an index appears twice on one
line, there is an idiom for it. Any time a temporary list is built only to be
looped over, there is a comprehension or a generator. Neither is about elegance; both remove an index, and an index is where the
bug lives. Where the values themselves come from
and what may be done to them is
what a
value is, and what may be done to it
, and the modules that already contain
most of what you are about to write are
what ships
with Python, and saves you a dependency
. Both sit inside
readable, everywhere, and the usual
first language
.

Alongside this

Above this