I've been converting if-elif-chains to structural pattern matching but am having difficulty with inverted tests.
It is easy to make cases that match any of the supported patterns (literal, class, mapping, sequence, etc). How do I make a case for a negative match?
For example, I need to coerce an object when its type doesn't match:
if isinstance(x, (list, dict)):
x = json.dump(x)
elif not isinstance(x, str): # <-- Inverted test
x = str(x)
Intrinsically, the design of structural pattern matching only triggers a case when there is a positive match. However, there are two workarounds.
The easiest way is to add a guard expression. But this is a last resort because it doesn't take advantage of the pattern matching and destructuring capabilities.
The second way is to add an earlier matching test so that later cases can presume that the inverse match is true. This works nicely if the negative case was intended to be the last case. If not, it becomes a little awkward because nesting is required.
Take the inverted test and move it into an if-expression:
match x:
case list() | dict():
x = json.dump(x)
case _ if not isinstance(x, str): # <-- Inverted test
x = str(x)
The basic idea here is to make a positive match so that subsequent cases can assume a negative match:
match x:
case list() | dict():
x = json.dump(x)
case str(): # <-- Positive match
pass
case _: # <-- Inverted case
x = str(x)
The pretest technique is elegant unless you there are additional cases to be matched:
if isinstance(x, (list, dict)):
x = json.dump(x)
elif not isinstance(x, str): # <-- Inverted test
x = str(x)
elif x == 'quit': # <-- Additional test
sys.exit(0)
The easiest solution here is to to move the additional test to occur before the inverted test:
match x:
case list() | dict():
x = json.dump(x)
case 'quit': # <-- Moved the test up
sys.exit(0)
case str(): # <-- Positive match
pass
case _: # <-- Inverted case
x = str(x)
It's not always possible to reorder the tests. If so, then a new level of nested matching can be introduced:
case list() | dict():
x = json.dump(x)
case str(): # <-- Positive match
match x: # <-- Nested match
case 'quit': # <-- Inner case
sys.exit(0)
case _: # <-- Inverted case
x = str(x)