I keep learning small Python things that quietly change how I write everything after them. This is where I write them down as I find them — one idea per entry, each one something I actually reached for that week. It will grow.
First entry, and a good one to start on, because it fooled me: match.
The switch Python withheld for thirty years
Every other language handed you a switch on day one. Python didn’t. For decades the answer to “how do I switch on a value?” was a ladder of if / elif, or a dictionary of functions if you were feeling clever.
Then 3.10 arrived with match — and everyone said finally, a switch. That framing is the trap. match is not a switch. A switch compares a value against constants. match compares a value against shapes, and pulls the pieces out while it’s at it. Once you see it that way, you stop writing a whole category of fiddly code.
Let me show you the thing I mean.
The pain, so the fix means something
Say we’re reading commands for a little robot. Each command comes in as a list of tokens — ["move", 10], ["move", 10, "fast"], ["quit"]. Here’s the honest if/elif version:
def run(cmd):
if len(cmd) == 2 and cmd[0] == "move":
distance = cmd[1]
return f"Moving {distance} steps"
elif len(cmd) == 3 and cmd[0] == "move":
distance, speed = cmd[1], cmd[2]
return f"Moving {distance} steps, {speed}"
elif len(cmd) == 1 and cmd[0] == "quit":
return "Bye"
else:
return "I don't understand"Look at what your eyes have to do on every branch: check the length, then check the first element, then manually index out the rest. You are describing the shape of the data in prose — len == 2 and [0] == "move" — and then rebuilding it by hand. The shape is in your head; the code only hints at it.
Let the shape be the code
Now the same thing with match:
Run it. Read one case line: case ["move", distance]. It says two things at once — “is this a two-item list whose first item is the string move?” and, if so, “call the second item distance.” The match and the unpacking are the same act. The code now looks like the data it’s about. That’s the whole idea; everything else is detail.
Two things worth saying out loud, because they’re not obvious:
"move"is a literal — it must equal exactly.distanceis a capture — it matches anything and binds the name. A pattern is a mix of “this exact thing” and “whatever’s here, name it.”- First matching case wins, and there is no fall-through. No
break. It behaves the way you already hopedswitchdid.
Matching on the list’s shape, not its contents
The list patterns compose the way lists do. You can match on length and structure alone:
case [only] matches a list of exactly one element and hands it to you as only. case [first, *rest] is the star pattern — grab the head, sweep the rest into a list. This is the head/tail decomposition from every recursion lecture, except now it’s a first-class piece of syntax. You can match case [first, *middle, last] too; Python figures out the middle.
The one that bit me
Here is the mistake I made, and I think everyone makes it once. I wanted to match against a named constant:
NORTH = "north"
match direction:
case NORTH: # looks like a comparison. it is NOT.
return "going up"
case _:
return "elsewhere"This always matches, no matter what direction is. Because a bare name in a case is a capture, not a comparison — case NORTH means “match anything and rebind the name NORTH to it.” I had silently reassigned my own constant and made the first branch a catch-all.
The rule is sharp and worth memorising: in a pattern, a bare name captures; to compare, you need a literal (case "north"), a dotted name (case Compass.NORTH — enums and attributes are treated as values), or a guard:
The if after a pattern is a guard — the case matches only if the pattern fits and the condition holds. It’s the escape hatch for anything the shape alone can’t say.
case _ — the floor under everything
_ is the wildcard: it matches anything and — unlike a bare name — binds nothing. It’s your default, and putting it last turns “no branch matched” from a silent None into a deliberate answer. Leave it off and a match that falls through simply does nothing, which is a bug that hides well. I always write the _.
The mental model I keep now
Stop thinking switch. Think of a case as a question about shape, with blanks in it. The data either fits the shape or it doesn’t; the first shape that fits wins; and the blanks are boxes the data falls into so you can use it on the next line. Literals are the parts that must match exactly, names are the parts you’re pulling out, _ is “I don’t care,” and a guard is the fine print.
Written that way, a parser reads like a picture of the grammar. That’s the trick, and it’s why match earns its place in the language — not because Python finally got a switch, but because it got something the switch never was.
Next time: probably dataclasses, or the walrus — whichever one trips me up first.