Real-world match/case
source link: https://nedbatchelder.com/blog/202312/realworld_matchcase.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Real-world match/case
Python 3.10 brought us structural pattern matching, better known as match/case. At first glance, it looks like a switch statement from C or JavaScript, but it’s very different.
You can use match/case to match specific literals, similar to how switch statements work, but their point is to match patterns in the structure of data, not just values. PEP 636: Structural Pattern Matching: Tutorial does a good job explaining the mechanics, but feels like a toy example.
Here’s a real-world use: at work we have a GitHub bot installed as a webhook. When something happens in one of our repos, GitHub sends a payload of JSON data to our bot. The bot has to examine the decoded payload to decide what to do.
These payloads are complex: they are dictionaries with only 6 or 8 keys, but they are deeply nested, eventually containing a few hundred pieces of data. Originally we were picking them apart to see what keys and values they had, but match/case made the job much simpler.
Here’s some of the code for determining what to do when we get a “comment created” event:
# Check the structure of the payload:
match event:
case {
"issue": {"closed_at": closed},
"comment": {"created_at": commented},
} if closed == commented:
# This is a "Close with comment" comment. Don't do anything for the
# comment, because we'll also get a "pull request closed" event at
# the same time, and it will do whatever we need.
pass
case {"sender": {"login": who}} if who == get_bot_username():
# When the bot comments on a pull request, it causes an event, which
# gets sent to webhooks, including us. We don't have to do anything
# for our own comment events.
pass
case {"issue": {"pull_request": _}}:
# The comment is on a pull request. Process it.
return process_pull_request_comment(event)
The first case matches if the dict has an “issue” key containing a dict with a “closed_at” key and also a “comment” key containing a dict with a “created_at” key, and if those two leaves in the dict are equal. Writing out that condition without match/case would be more verbose and confusing.
The second case examines the event to see if the bot was the originator of the event. This one wouldn’t have been so hard to write in a different way, but match/case makes it nicer.
This is just what match/case is good at: checking patterns in the structure of data.
It’s also interesting to see the bytecode generated. For that first case, it looks like this:
2 0 LOAD_GLOBAL 0 (event)
3 2 MATCH_MAPPING
4 POP_JUMP_IF_FALSE 67 (to 134)
6 GET_LEN
8 LOAD_CONST 1 (2)
10 COMPARE_OP 5 (>=)
12 POP_JUMP_IF_FALSE 67 (to 134)
4 14 NOP
5 16 NOP
3 18 LOAD_CONST 8 (('issue', 'comment'))
20 MATCH_KEYS
22 POP_JUMP_IF_FALSE 65 (to 130)
24 DUP_TOP
26 LOAD_CONST 4 (0)
28 BINARY_SUBSCR
4 30 MATCH_MAPPING
32 POP_JUMP_IF_FALSE 64 (to 128)
34 GET_LEN
36 LOAD_CONST 5 (1)
38 COMPARE_OP 5 (>=)
40 POP_JUMP_IF_FALSE 64 (to 128)
42 LOAD_CONST 9 (('closed_at',))
44 MATCH_KEYS
46 POP_JUMP_IF_FALSE 62 (to 124)
48 DUP_TOP
50 LOAD_CONST 4 (0)
52 BINARY_SUBSCR
54 ROT_N 7
56 POP_TOP
58 POP_TOP
60 POP_TOP
62 DUP_TOP
64 LOAD_CONST 5 (1)
66 BINARY_SUBSCR
5 68 MATCH_MAPPING
70 POP_JUMP_IF_FALSE 63 (to 126)
72 GET_LEN
74 LOAD_CONST 5 (1)
76 COMPARE_OP 5 (>=)
78 POP_JUMP_IF_FALSE 63 (to 126)
80 LOAD_CONST 10 (('created_at',))
82 MATCH_KEYS
84 POP_JUMP_IF_FALSE 61 (to 122)
86 DUP_TOP
88 LOAD_CONST 4 (0)
90 BINARY_SUBSCR
92 ROT_N 8
94 POP_TOP
96 POP_TOP
98 POP_TOP
100 POP_TOP
102 POP_TOP
104 POP_TOP
106 STORE_FAST 0 (closed)
108 STORE_FAST 1 (commented)
6 110 LOAD_FAST 0 (closed)
112 LOAD_FAST 1 (commented)
114 COMPARE_OP 2 (==)
116 POP_JUMP_IF_FALSE 70 (to 140)
10 118 LOAD_CONST 0 (None)
120 RETURN_VALUE
3 >> 122 POP_TOP
>> 124 POP_TOP
>> 126 POP_TOP
>> 128 POP_TOP
>> 130 POP_TOP
132 POP_TOP
>> 134 POP_TOP
136 LOAD_CONST 0 (None)
138 RETURN_VALUE
6 >> 140 LOAD_CONST 0 (None)
142 RETURN_VALUE
That’s a lot, but you can see roughly what it’s doing: check if the value is a mapping (dict) with at least two keys (bytecodes 2–12), then check if it has the two specific keys we’ll be examining (18–22). Look at the value of the first key, check if it’s a dict with at least one key (24–40), etc, and so on.
Hand-writing these sorts of checks might result in shorter bytecode. For example, I already know the event value is a dict, since that is what the GitHub API promise me, so there’s no need to check it explicitly each time. But the Python code would be twistier and harder to get right. I was initially a skeptic about match/case, but this example shows a clear benefit.
#python» react
Recommend
-
29
Music Match - Game On World Music - NEXT
-
12
What is a real world use case for splitting a string in JavaScript?
-
13
-
2
If reading about code with math examples is the bane of your existence, keep reading. This post uses relatable examples with sweaters and dogs. Jump to: Once upon a time, in 2006, a Python Enhancement Proposal(PEP...
-
3
GreenPower Network: Real World Use-Case October 21, 2022
-
5
ICPC World Finals: Ecnerwala vs Errichto Kotlin Match ICPC World Finals: Ecnerwala vs Errichto Kotlin Match ...
-
5
All about usability heuristic #2: Match between the system and the real worldLet’s count down the instances where we could have seen the violation of this heuristic principle -Ever worked wi...
-
6
- Advertisement -It’s easy to draw correlations between big data and certain areas of the business world. For example, it makes sense that a tech company would leverage big data, or...
-
7
Learn why this is the correct answer IBM's Deep Blue system achieved its first victory ov...
-
5
Bees and ETRVs: An unlikely match-up of the natural world and electric trackless rubber-tired vehicles by Tsinghua Univer...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK