Beginner · Lesson 49 of 94

Idioms you'll meet in real code

Watch this lesson

View full playlist ↗

Read the lesson

What you'll learn

Concept

Every language has phrases that fluent speakers use without thinking and newcomers stumble over. In Python they are short, they appear constantly in libraries and code review, and none of them is hard once named. This lesson collects the ones that most often stall a reader who knows the basics: you will see all of them in the Advanced lessons and in every open-source project you open.

Idiom Looks like Means
conditional expression label = "even" if n % 2 == 0 else "odd" pick one of two values in one expression
chained comparison 0 <= x < 10 0 <= x and x < 10
walrus operator if (m := pattern.search(text)): assign and test in one step
star-unpacking first, *rest = items; [*a, *b]; {**d1, **d2} spread a collection into places
identity test if value is None: is this the very same object?
truthiness if items: / if not name: non-empty / empty
swap a, b = b, a exchange without a temporary
enumerate and zip for i, (a, b) in enumerate(zip(xs, ys)): index plus paired items

The conditional expression, x if condition else y, is Python's ternary. It is an expression, so it can sit inside a function call, an f-string, or a comprehension. Use it for a genuine two-way choice of value; the moment either branch does work rather than yielding a value, write an if statement. Chained comparisons read as math, low <= x < high, and are both shorter and less error-prone than two comparisons joined with and.

The walrus operator :=, added in 3.8, assigns a value and gives it back, so you can capture a result in the same line that tests it. Its home is loops and conditions where you would otherwise compute a value, assign it, then test it on the next line: reading chunks until empty, matching a pattern and using the match. It is easy to overuse; if the line needs a second read, split it.

Idioms you'll meet in real code: m = pattern.search(text); if m:; use(m) → if (m := pattern.search(text)):; use(m)
Idioms you'll meet in real code · Figure 1 · Open for full size

Star-unpacking appears in three places, and it is the same star you met in function arguments. In assignments, first, *rest = items splits a sequence into a head and the remainder, and *init, last = items splits off the tail. In calls, f(*args, **kwargs) spreads a sequence and a dictionary back into parameters. In literals, [*a, *b] joins two lists and {**defaults, **overrides} merges two dictionaries with later keys winning, and both build a new object instead of changing either side.

is and == answer different questions. == asks whether two values are equal, and any class decides what equal means by defining __eq__. is asks whether two names point at the very same object, and no class can override it. That is why value is None is the idiom and value == None is not: None exists exactly once per program, so identity is the precise question, and someone else's __eq__ can never fool it. Use == for every other comparison, and if value: when you genuinely mean "not empty and not zero", but be deliberate when zero is a legitimate value.

Idioms you'll meet in real code: use is / is not → use == / != → if value: / if not value: → compare explicitly: if value is None / if value == 0
Idioms you'll meet in real code · Figure 2 · Open for full size

Code

The conditional expression and chained comparisons in the places they belong: inside f-strings, calls, and comprehensions.

# conditional.py
for n in (3, 8, 15):
    print(f"{n} is {'even' if n % 2 == 0 else 'odd'} and {'small' if n < 10 else 'big'}")

scores = [45, 72, 100, 101]
print(["valid" if 0 <= s <= 100 else "invalid" for s in scores])

x = 7
print(0 < x < 10, 0 < x < 5, 1 < 2 < 3 < 4)
Output
3 is odd and small
8 is even and small
15 is odd and big
['valid', 'valid', 'valid', 'invalid']
True False True

The walrus operator where it earns its place: a read loop and a regex match. The before/after for the match shows the line it saves.

# walrus.py
import io
import re

stream = io.StringIO("abcdefghij")
chunks = []
while (chunk := stream.read(4)):
    chunks.append(chunk)
print(chunks)

pattern = re.compile(r"\d+")
texts = ["order 42", "no numbers", "room 7"]
for text in texts:
    if (m := pattern.search(text)):
        print(text, "->", int(m.group()))
    else:
        print(text, "-> none")

numbers = [4, 9, 16]
print([root for n in numbers if (root := n ** 0.5) == int(root)])
Output
['abcd', 'efgh', 'ij']
order 42 -> 42
no numbers -> none
room 7 -> 7
[2.0, 3.0, 4.0]

Star-unpacking in assignments and literals, and the identity/equality distinction. Two equal lists are not the same object; None is always the same object.

# stars_identity.py
first, *middle, last = [1, 2, 3, 4, 5]
print(first, middle, last)

a, b = [1, 2], [3, 4]
print([*a, *b], (*a, 0, *b))

defaults = {"theme": "dark", "size": 12}
print({**defaults, "size": 16, "lang": "en"})

x = [1, 2, 3]
y = [1, 2, 3]
z = x
print(x == y, x is y, x is z)
print(None is None, [] == [], [] is [])
Output
1 [2, 3, 4] 5
[1, 2, 3, 4] (1, 2, 0, 3, 4)
{'theme': 'dark', 'size': 16, 'lang': 'en'}
True False True
True True False

None handling and truthiness done deliberately. The default None sentinel is distinguished from a legitimate zero, and a mutable default is avoided the idiomatic way.

# none_handling.py
def describe(count=None):
    if count is None:
        return "not counted yet"
    if not count:
        return "counted: nothing"
    return f"counted: {count}"


print(describe(), "|", describe(0), "|", describe(5))


def append_tag(tag, tags=None):
    tags = [] if tags is None else tags
    tags.append(tag)
    return tags


print(append_tag("a"), append_tag("b"), append_tag("c", ["x"]))
name = ""
print("anonymous" if not name else name)
Output
not counted yet | counted: nothing | counted: 5
['a'] ['b'] ['x', 'c']
anonymous

Try it yourself

Create idioms.py and rewrite each of these the idiomatic way, printing before and after: a four-line if/else that sets a label into a conditional expression; x > 0 and x < 100 into a chained comparison; a line = f.readline() / while line: loop into a walrus loop; list1 + list2 and d1.copy(); d1.update(d2) into star forms; and a function with a [] default into the None sentinel pattern.

Common mistakes

What's next

You can read Python the way Python developers write it. Next you look under the for loop and learn what iteration really is: iterators and the iteration protocol.

Course overview