Structural Python Matching with variable pattern keys

166 Views Asked by At

As part of a larger problem, I am trying to write a function that uses pattern matching in order to extract fields from a given dictionary.

For example, given a dictionary like:

example = {
    'want1': [1, 2, 3],
    'want2': ['a', 'b', 'c'],
    'want3': [1.1, 2.2, 3.3], 
    'want4': 'hello', 
}

I want to write a function that retrieves the value there if it exists, makes sure it is a list and return it.

Something like:

extract('want1', example) # [1, 2, 3]
extract('want2', example) # ['a', 'b', 'c']
extract('want4', example) # None
extract('want5', example) # None

While this problem isn't that difficult, as a learning process I am looking into structural pattern matching and I came up with the following (wrong) solution:

def extract(field, data): 
    match data: 
        case {field: list() as values}: 
            return values

This function has a syntax error because of the field variable as a key in the pattern.

Looking further into it I found out that keys in patterns can only be literals or attribute lookups.

Based on this, I then tried to hack the following:

from types import SimpleNamespace

def extract(field, data): 
    lookup = SimpleNamespace(field=field)

    match data:
        case { lookup.field: list() as values}: 
            return values

and strangely enough this works.

My feeling is that the above is a hack to get around something that was specifically avoided for a reason. If this works then why does a variable as a key not work?

Also, would there be a better way of doing this?

I could do this without pattern matching, but I feel that it offers quite some benefits in not having to explicitly check that the value exists in the dictionary and also not having to explicitly check that the value is a list.

1

There are 1 best solutions below

0
Gábor Fekete On
def extract(field, data):
    match isinstance(value:=data.get(field,None),list):
        case True:
            return value
    return None

This is a really bad example usage of pattern matching. This can be solved by a much simpler function:

def extract(field, data):
    value = data.get(field,None)
    return value if isinstance(value,list) else None

Outputs (for both functions):

for i in range(1,6):
    field = f'want{i}'
    print(field,':', extract(field,example))

# Output:

want1 : [1, 2, 3]
want2 : ['a', 'b', 'c']
want3 : [1.1, 2.2, 3.3]
want4 : None
want5 : None