Search code examples
pythonpattern-matchingstructural-pattern-matching

Subpattern/partial pattern matching in python


lst = [1, 2, 3]
dct = {'a': 1, 'b': 2}
tpl = (1, 2, 3)
strng = 'abc'
dct_in_lst = [dct]

print('match variation 1')
for i in [lst, dct, tpl, strng, dct_in_lst]:
  match i:
    case []: print('list', i, type(i))
    case {}: print('dict', i, type(i))
    case (): print('tuple', i, type(i))
    case '': print('string', i, type(i))
    case [{}]: print('dictionary inside list', i, type(i))

print('\nmatch variation 2')
for i in [lst, dct, tpl, strng]:
  match i:
    case list(): print('list', i, type(i))
    case dict(): print('dict', i, type(i))
    case tuple(): print('tuple', i, type(i))
    case str(): print('string', i, type(i))

outputs,

match variation 1
dict {'a': 1, 'b': 2} <class 'dict'>
dictionary inside list [{'a': 1, 'b': 2}] <class 'list'>

match variation 2
list [1, 2, 3] <class 'list'>
dict {'a': 1, 'b': 2} <class 'dict'>
tuple (1, 2, 3) <class 'tuple'>
string abc <class 'str'>

is there a specific reasoning that dictionaries get matched for partial/subpatterns also.


Solution

  • Is there a specific reasoning that dictionaries get matched for partial/subpatterns also.

    While the PEP mentions "natural subtyping behavior for extra keys", that is not the actual reason.

    Python's structural pattern matching feature was modeled after that found in other languages. The rationale for the partial match behavior is entirely use case driven.

    In particular, partial matching is necessary to support SQL style queries. In SQL, the SELECT clause names the fields to be extracted and the WHERE clause only touches specific fields of interest. None of the unused fields need to be enumerated because they don't contribute to the essence of the query.

    In Python, we may be given this example dataset:

    team_history = [
        {"date": "3/14/2013", "score": 14, "result": "lost"},
        {"date": "6/2/2013", "score": 15, "result": "lost"},
        {"date": "7/15/2013", "score": 1, "result": "won"},
        {"date": "8/14/2013", "score": 4, "result": "lost"},
        {"date": "8/19/2013", "score": 7, "result": "won"},
        {"date": "9/2/2013", "score": 2, "result": "lost"},
        {"date": "9/7/2013", "score": 5, "result": "won"},
        {"date": "10/2/2013", "score": 3, "result": "lost"},
        {"date": "10/5/2013", "score": 12, "result": "won"}
    ]
    

    Using Python's match/case with the mapping pattern's partial matching, we can support SQL-style queries:

    # SELECT date FROM History WHERE result = "won";
    
    for game in team_history:
        match game:
            case {'result': 'won', 'date': date}:
                print(f'On {date}, we won the game!')
    

    This outputs:

    On 7/15/2013, we won the game!
    On 8/19/2013, we won the game!
    On 9/7/2013, we won the game!
    On 10/5/2013, we won the game!
    

    In this example, it is very convenient to ignore the score field. In examples with even more fields, the partial match feature becomes essential.