3.10. OOP Pattern Matching

3.10.1. Rationale

Let's take the following code and do the refactoring:

3.10.2. Problem

>>> language = 'English'
>>>
>>> if language == 'Polish':
...     result = 'Cześć'
... elif language == 'English':
...     result = 'Hello'
... elif language == 'German':
...     result = 'Guten Tag'
... elif language == 'Russian':
...     result = 'Здравствуй'
... elif language == 'Chinese':
...     result = '你好'
... elif language == 'French':
...     result = 'Bonjour'
... else:
...     result = 'Unknown language'
>>>
>>> print(result)
Hello

3.10.3. Switch

In other languages you may find switch statement: (note that this is not a valid Python code)

>>> language = 'English'
>>>
>>> switch(language):  
...     case 'Polish': result = 'Cześć'
...     case 'English': result = 'Hello'
...     case 'German': result = 'Guten Tag'
...     case 'Russian': result = 'Здравствуй'
...     case 'Chinese': result = '你好'
...     case 'French': result = 'Bonjour'
...     default: result = 'Unknown language'
>>>
>>> print(result)  
Hello

3.10.4. Pattern Matching

  • Since Python 3.10: PEP 636 -- Structural Pattern Matching: Tutorial

>>> language = 'English'
>>>
>>> match language:  
...     case 'Polish': result = 'Cześć'
...     case 'English': result = 'Hello'
...     case 'German': result = 'Guten Tag'
...     case 'Russian': result = 'Здравствуй'
...     case 'Chinese': result = '你好'
...     case 'French': result = 'Bonjour'
...     case _: result = 'Unknown language'
>>>
>>> print(result)
Hello

3.10.5. Behavior

Table 3.2. Pattern Matching 1

Statement

Meaning

x

assign x = subject

'x'

test subject == 'x'

x.y

test subject == x.y

x()

test isinstance(subject, x)

{'x': 'y'}

test isinstance(subject, Mapping) and subject.get('x') == 'y'

['x']

test isinstance(subject, Sequence) and len(subject) == 1 and subject[0] == 'x'

3.10.6. Matching Alternatives

>>> status = 418
>>>
>>>
>>> match status:  
...     case 400:
...         result = 'Bad request'
...     case 401 | 403 | 405:
...         result = 'Not allowed'
...     case 404:
...         result = 'Not found'
...     case 418:
...         result = "I'm a teapot"
...     case _:
...         result = 'Unexpected status'

3.10.7. Matching on Sequence

>>> request = 'GET /index.html HTTP/2.0'
>>>
>>>
>>> match request.split():  
...     case ['GET', uri, version]:
...         server.get(uri)
...     case ['POST', uri, version]:
...         server.post(uri)
...     case ['PUT', uri, version]:
...         server.put(uri)
...     case ['DELETE', uri, version]:
...         server.delete(uri)

3.10.8. Matching on Sequence with Assignment

>>> class Hero:
...     def action(self):
...         return  ['move', 'left', 20]
>>>
>>>
>>> match hero.action():  
...     case ['move', ('up'|'down'|'left'|'right') as direction, value]:
...         hero.move(direction, value)
...     case ['make_damage', value]:
...         hero.make_damage(value)
...     case ['take_damage', value]:
...         hero.take_damage(value)

3.10.9. Matching on Enum

>>> from enum import Enum
>>>
>>>
>>> class Key(Enum):
...     ESC = 27
...     ARROW_LEFT = 37
...     ARROW_UP = 38
...     ARROW_RIGHT = 39
...     ARROW_DOWN = 40
>>>
>>>
>>> match keyboard.on_key_press():  
...     case Key.ESC:
...         game.quit()
...     case Key.ARROW_LEFT:
...         hero.move_left()
...     case Key.ARROW_UP:
...         hero.move_up()
...     case Key.ARROW_RIGHT:
...         hero.move_right()
...     case Key.ARROW_DOWN:
...         hero.move_down()
...     case _:
...         raise ValueError(f'Unrecognized key')

3.10.10. References

1

Raymond Hettinger. Retrieved: 2021-03-07. URL: https://twitter.com/raymondh/status/1361780586570948609?s=20

3.10.11. Assignments