'Structural pattern matching using regex

I have a string that I'm trying to validate against a few regex patterns and I was hoping since Pattern matching is available in 3.10, I might be able to use that instead of creating an if-else block.

Consider a string 'validateString' with possible values 1021102,1.25.32, string021.

The code I tried would be something like the following.

match validateString:
    case regex1:
        print('Matched regex1')
    case regex2:
        print('Matched regex2')
    case regex3:
        print('Matched regex3')

For regex 1, 2 and 3, I've tried string regex patterns and also re.compile objects but it doesn't seem to work.

I have been trying to find examples of this over the internet but can't seem to find any that cover regex pattern matching with the new python pattern matching.

Any ideas for how I can make it work?

Thanks!



Solution 1:[1]

It is not possible to use regex-patterns to match via structural pattern matching (at this point in time).

From: PEP0643: structural-pattern-matching

PEP 634: Structural Pattern Matching
Structural pattern matching has been added in the form of a match statement and case statements of patterns with associated actions. Patterns consist of sequences, mappings, primitive data types as well as class instances. Pattern matching enables programs to extract information from complex data types, branch on the structure of data, and apply specific actions based on different forms of data. (emphasis mine)

Nothing in this gives any hint that evoking match / search functions of the re module on the provided pattern is intended to be used for matching.


You can find out more about the reasoning behind strucutral pattern matching by reading the actuals PEPs:

they also include ample examples on how to use it.

Solution 2:[2]

As Patrick Artner correctly points out in the other answer, there is currently no official way to do this. Hopefully the feature will be introduced in a future Python version and this question can be retired. Until then:

PEP 634 specifies that Structural Pattern Matching uses the == operator for evaluating a match. We can override that.

import re
from dataclasses import dataclass

# noinspection PyPep8Naming
@dataclass
class regex_in:
    string: str

    def __eq__(self, other: str | re.Pattern):
        if isinstance(other, str):
            other = re.compile(other)
        assert isinstance(other, re.Pattern)
        # TODO extend for search and match variants
        return other.fullmatch(self.string) is not None

Now you can do something like:

match regex_in(validated_string):
    case r'\d+':
        print('Digits')
    case r'\s+':
        print('Whitespaces')
    case _:
        print('Something else')

Caveat #1 is that you can't pass re.compile'd patterns to the case directly, because then Python wants to match based on class. You have to save the pattern somewhere first.

Caveat #2 is that you can't actually use local variables either, because Python then interprets it as a name for capturing the match subject. You need to use a dotted name, e.g. putting the pattern into a class or enum:

class MyPatterns:
    DIGITS = re.compile('\d+')

match regex_in(validated_string):
    case MyPatterns.DIGITS:
        print('This works, it\'s all digits')

Groups

This could be extended even further to provide an easy way to access the re.Match object and the groups.

# noinspection PyPep8Naming
@dataclass
class regex_in:
    string: str
    match: re.Match = None

    def __eq__(self, other: str | re.Pattern):
        if isinstance(other, str):
            other = re.compile(other)
        assert isinstance(other, re.Pattern)
        # TODO extend for search and match variants
        self.match = other.fullmatch(self.string)
        return self.match is not None

    def __getitem__(self, group):
        return self.match[group]

# Note the `as m` in in the case specification
match regex_in(validated_string):
    case r'\d(\d)' as m:
        print(f'The second digit is {m[1]}')
        print(f'The whole match is {m.match}')

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2