Customizable code for easy changes and future demands.
2 Different attitudes.
You can subclass it, change regex parser, act functions and etc..
import re    
class Executor:
    def __init__(self, action_dict):
        self.action_dict = action_dict
        self.keys = list(action_dict.keys())
    def regex_parser(self, regex):
        return regex
    def string_parser(self, string):
        return string.lower()
    def match(self, string):
        for key in self.keys:
            regex = re.compile(self.regex_parser(key))
            if regex.search(self.string_parser(string)) is not None:
                return self.act(key, string)
        return None
    def act(self, key, string):
        func = self.action_dict[key]
        return func(string)
executor = Executor(
    {
        "test": print,
        "some": lambda s: print(s + " Hello matcher"),
        "end$": lambda s: print("End"),
    }
)
executor.match("testing")
executor.match("something")
executor.match("LegenD")
>>> testing
>>> something Hello matcher
>>> End
Second attitude is more verbose, but benefit is that each Matcher class can have its own set of rules and evaluation.
import re
class DefaultMatcher:
    regex = "default"
    def regex_parser(self, regex):
        return regex
    def string_parser(self, string):
        return string.lower()
    def function(self, string, *args, **kwargs):
        """This function is the action."""
        return args, kwargs
    def match(self, string, *args, **kwargs):
        # Returns something or None
        return self.compiled_regex.search(self.string_parser(string))
    def __init__(self, *args, **kwargs):
        self.compiled_regex = re.compile(self.regex_parser(self.regex))
    def __call__(self, string, *args, **kwargs):
        parsed_string = self.string_parser(string)
        if self.match(string):
            return self.function(string, *args, **kwargs)
        else:
            return None
class Matcher1(DefaultMatcher):
    regex = "test1"
    def function(self, *args, **kwargs):
        return "Matcher_1"
class Matcher2(DefaultMatcher):
    regex = "test2"
    def function(self, *args, **kwargs):
        return "Matcher_2"
class Matcher3(DefaultMatcher):
    regex = "o"
    def regex_parser(self, regex):
        super_regex = super().regex_parser(regex)
        return super_regex + "$"
    def function(self, *args, **kwargs):
        return "Matcher_3"
class DefaultExecutor:
    def __init__(self, list_of_matcher, *args, **kwargs):
        self.list_of_matcher = [matcher(*args, **kwargs) for matcher in list_of_matcher]
    def __call__(self, string, *args, **kwargs):
        for matcher in self.list_of_matcher:
            result = matcher(string, *args, **kwargs)
            if result is not None:
                return result
executor = DefaultExecutor([Matcher1, Matcher2, Matcher3])
print(executor("test1"))
print(executor("Otto"))
print(executor("test2"))