The When of Python Blog

Guidance on when to use Python features (and when not to).
Comment your opinions on our articles to join the discussion.
Follow us on Twitter and GitHub to get the latest updates first.

Match Case - A Simple Switch Statement for Python?


What has your experience of match case (Structural Pattern Matching) been? Do you agree with this post? Do you disagree? Please comment below.

Simple Switch Statement at Last?

Python has always lacked a switch statement to perform conditional logic e.g.

## Note - not actual Python
case foo:
    when eggs:
        return 'spam'
    when knights:
        return 'Ni!'
    ...

The switch approach can be found in languages like PHP and also in SQL (CASE WHEN THEN END etc).

Instead, Python has relied largely on if-elif-else constructs and, to a lesser extent, dictionaries - for example:

mapper = {'eggs': 'spam', 'knights': 'Ni!', ...}
result = mapper['eggs']

In Python 3.10, Structural Pattern Matching (match case) was added which raised some questions. Was this basically a switch statement on steroids? Should we all start using this for our conditional logic? And could it be time to largely retire if-elif-else constructs? Initial impressions were positive. Certainly the higher level syntax for Structural Pattern Matching looks clean and elegant:

match food:
    case 'eggs':
        return 'spam'
    case ...

Unfortunately major concerns arise when we take a closer look.

Python Pattern Matching Problems

New Mini Language

Structural Pattern Matching is a new mini language in Python - a powerful language that looks like normal Python but isn't. And that is where the main gotchas come from.

Similarity to Object Instantiation Misleading

Imagine we have a Point class:

class Point:
    def __init__(self, x, y):
        pass

case Point(x, y): seems to me to be an obvious way of looking for such a Point object and unpacking its values into x and y but it isn't allowed. It is a perfectly valid syntax for instantiating a Point object but we are not instantiating an object and supplying the object the case condition – instead we are supplying a pattern to be matched and unpacked. We have to have a firm grasp on the notion that Python patterns are not objects. If we forget we get a TypeError:

case Point(0, 999):

## TypeError: Point() accepts 0 positional sub-patterns (2 given)

Note, we must match the parameter names (the left side) but can unpack to any variable names we like (the right side). For example, all of the following will work:

case Point(x=x, y=y):
case Point(x=lon, y=lat):
case Point(x=apple, y=banana):

but

case Point(a=x, b=y):

will not.

It is a bit disconcerting being forced to use what feel like keyword arguments in our patterns when the original class definition is optionally positional. We should expect lots of mistakes here and it will require concentration to pick them up in code review.

Similarity to isinstance Misleading

case Point:, case int:, case str:, case float: don’t work as you might expect. The proper approach is to supply parentheses: using the example of integer patterns, we need case int():, or, if we want to "capture" the value into, say, x, case int(x):. But if we don't know about the need for parentheses, or we slip up (easy to do) these inadvertant patterns will match anything and assign it to the name Point or int or str etc. Definitely NOT what we want.

the builtin str is now broken – hopefully obviously

The only protection against making this mistake is when you accidentally do this before other case conditions – e.g.

case int:
^
SyntaxError: name capture 'int' makes remaining patterns unreachable

Otherwise you are on your own and completely free to make broken code. This will probably be a common error because of our experience with isinstance where we supply the type e.g. isinstance(x, int). Which reminds me of a passage in Through the Looking-Glass, and What Alice Found There by Lewis Carroll.

A Bread-and-Butterfly

‘Crawling at your feet,’ said the Gnat (Alice drew her feet back in some alarm), ‘you may observe a Bread-and-Butterfly. Its wings are thin slices of Bread-and-butter, its body is a crust, and its head is a lump of sugar.’

‘And what does it live on?’

‘Weak tea with cream in it.’

A new difficulty came into Alice’s head. ‘Supposing it couldn’t find any?’ she suggested.

‘Then it would die, of course.’

‘But that must happen very often,’ Alice remarked thoughtfully.

‘It always happens,’ said the Gnat. After this, Alice was silent for a minute or two, pondering.

Use Cases (in Python)

OK - so we should probably avoid case match in any situation where it is simpler and safer to use if-elif-else or another alternative - which will almost be all cases. Although Pattern Matching might be a great feature in Scala, it doesn't appear likely to be all that useful in a language like Python.

Having said that, match case is not completely without value for Python. Structural Pattern Matching does appear to be useful for parsing complex nested data such as ASTs or deeply nested JSON. 1

I do not recommend embracing match-case EXCEPT in the unusual cases where it's VERY handy.

The use case I had happened to be one of those weird cases (recursively matching the types and attribute values of Python AST nodes).

It can be pretty handy in some niche cases though.

(Trey Hunner - Python trainer)

But beyond that, it is hard to see the benefits outweighing the costs.

Verdict

Structural Pattern Matching should be for very narrow situational use only. Match case is not an important alternative for conditional logic in Python. In a narrow range of specialist cases it is probably the right tool for the job but it should not be thought of as Common Python.

  1. Trey Hunner - Twitter