What you'll learn
- Read and write the conditional expression, chained comparisons, and the walrus operator
- Use star-unpacking in assignments, calls, and literals
- Tell
isfrom==, and handleNonethe idiomatic way - Recognize the everyday idioms that make Python code look like Python
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.

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.

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)
3 is odd and small
8 is even and small
15 is odd and big
['valid', 'valid', 'valid', 'invalid']
True False TrueThe 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)])
['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 [])
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 FalseNone 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)
not counted yet | counted: nothing | counted: 5
['a'] ['b'] ['x', 'c']
anonymousTry 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
== None. It works by accident;is Noneis the idiom and the only correct test for identity.- Nesting conditional expressions.
a if x else b if y else cis legal and unreadable; useif/elif. - Walrus everywhere. It shines in loops and conditions that would otherwise repeat a computation; outside those, a plain assignment is clearer.
What's next
x if c else y,low <= x < high, and(name := expr)compress a choice, a range test, and an assign-and-test into one expression each.- Star-unpacking spreads collections in assignments, calls, and literals;
{**a, **b}merges dictionaries. isis forNone,True, andFalse;==for values; truthiness for emptiness, with zero handled deliberately.
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.