Search code examples
python-3.xregexcoding-style

Python regular expressions: Better way to handle non-matches?


When I deal with regular expressions, my code is littered with conditionals so as to not create exceptions when a pattern is not found:

m = some_compiled_pattern.match(s)
if m:
    x = m.groups()
    do_something_with(x)
m = some_other_compiled_pattern.search(s):
if m:
    y = m.groupdict()
else:
    y = {}
do_something_else_with(y)

Isn't there a better (less verbose) way to handle such exceptions?


Solution

  • You might find this class useful to reduce most of those if-no-match handling to a one line.

    class Returns:
        """
        Makes an object that pretends to have all possible methods, 
        but returns the same value (default None) no matter what this method, 
        or its arguments, is.
        """
    
        def __init__(self, return_val=None):
            self.return_val = return_val
    
            def the_only_method_there_is(*args, **kwargs):
                return return_val
    
            self.the_only_method_there_is = MethodType(the_only_method_there_is, self)
    
        def __getattr__(self, item):
            if not item.startswith('_') and item not in {'return_val', 'the_only_method_there_id'}:
                return self.the_only_method_there_is
            else:
                return getattr(self, item)
    

    Example use:

        >>> import re
        >>> p = re.compile(r'(\d+)\W+(\w+)')
        >>>
        >>> # when all goes well...
        >>> m = p.search('The number 42 is mentioned often')
        >>> num, next_word = m.groups()
        >>> num, next_word
        ('42', 'is')
        >>>
        >>> # when the pattern is not found...
        >>> m = p.search('No number here')
        >>> assert m is None  # m is None so...
        >>> num, next_word = m.groups()  # ... this is going to choke
        Traceback (most recent call last):
          ...
        AttributeError: 'NoneType' object has no attribute 'groups'
        >>>
        >>> # Returns to the rescue
        >>> num, next_word = (p.search('No number here') or Returns((None, 'default_word'))).groups()
        >>> assert num is None
        >>> next_word
        'default_word'
    

    EDIT: See this gist for a longer discussion (and alternate but similar solution) of this problem.