Python supports Structural Pattern Matching since version 3.10
.
I came to notice that matching an empty dict
doesn't work by simply matching {}
as it does for list
s.
According to my naive approach, non-empty dict
s are also matched (Python 3.10.4):
def match_empty(m):
match m:
case []:
print("empty list")
case {}:
print("empty dict")
case _:
print("not empty")
match_empty([]) # empty list
match_empty([1, 2]) # not empty
match_empty({}) # empty dict
match_empty({'a': 1}) # empty dict
Matching the constructors even breaks the empty list matching:
def match_empty(m):
match m:
case list():
print("empty list")
case dict():
print("empty dict")
case _:
print("not empty")
match_empty([]) # empty list
match_empty([1, 2]) # empty list
match_empty({}) # empty dict
match_empty({'a': 1}) # empty dict
Here is a solution, that works as I expect:
def match_empty(m):
match m:
case []:
print("empty list")
case d:
if isinstance(d, dict) and len(d) == 0:
print("empty dict")
return
print("not empty")
match_empty([]) # empty list
match_empty([1, 2]) # not empty
match_empty({}) # empty dict
match_empty({'a': 1}) # not empty
Now my questions are:
dict
(without checking the dict
length explicitly)?Using a mapping (dict) as the match pattern works a bit differently than using a sequence (list). You can match the dict's structure by key-value pairs where the key is a literal and the value can be a capture pattern so it is used in the case
.
You can use **rest
within a mapping pattern to capture additional keys in the subject. The main difference with lists is - "extra keys in the subject will be ignored while matching".
So when you use {}
as a case, what you're really telling Python is "match a dict, no constraints whatsoever", and not "match an empty dict". So one way that might be slightly more elegant than your last attempt is:
def match_empty(m):
match m:
case []:
print("empty list")
case {**keys}:
if keys:
print("non-empty dict")
else:
print("empty dict")
case _:
print("not empty")
I think the main reason this feels awkward and doesn't work good is because the feature wasn't intended to work with mixed types like this. i.e. you're using the feature as a type-checker first and then as pattern matching. If you knew that m
is a dict (and wanted to match its "insides"), this would work much nicer.