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 rowsrather thanr[0],r[1],r[2] enumerate(rows, start=1)when the position is wanted, which
gives(1, 'PL-14')and no counter to maintaindict(zip(codes, prices))builds{'PL-14': 240, 'PL-15': 180, 'PL-16': 95}in one linefirst, *rest = codesanswers 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: thenline[: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.