Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Context Manager for Control Flow

I'm wondering if something like this is possible in python (3.2, if that's relevant).

with assign_match('(abc)(def)', 'abcdef') as (a, b):
    print(a, b)

Where the behavior is:

  • if the regex matches, the regex groups get assigned to a and b
    • if there's a mismatch there, it'd throw an exception
  • if the match is None, it would just bypass the context entirely

My goal here is basically an extremely concise way of doing the contextual behavior.

I tried making the following context manager:

import re

class assign_match(object):
    def __init__(self, regex, string):
        self.regex = regex
        self.string = string
    def __enter__(self):
        result = re.match(self.regex, self.string)
        if result is None:
            raise ValueError
        else:
            return result.groups()
    def __exit__(self, type, value, traceback):
        print(self, type, value, traceback) #testing purposes. not doing anything here.

with assign_match('(abc)(def)', 'abcdef') as (a, b):
    print(a, b) #prints abc def
with assign_match('(abc)g', 'abcdef') as (a, b): #raises ValueError
    print(a, b)

It actually works exactly as desired for the case when the regexes match, but, as you can see, it throws the ValueError if there's no match. Is there any way I can get it to "jump" to the exit sequence?

Thanks!!

like image 484
Kurt Spindler Avatar asked Oct 08 '22 17:10

Kurt Spindler


1 Answers

Ah! Perhaps explaining it on SO clarified the problem for me. I just use an if statement instead of a with statement.

def assign_match(regex, string):
    match = re.match(regex, string)
    if match is None:
        raise StopIteration
    else:
        yield match.groups()

for a in assign_match('(abc)(def)', 'abcdef'):
    print(a)

Gives exactly the behavior I'd like. Leaving this here in case other people want to benefit from it. (Mods, if it's not relevant, feel free to delete, etc.)

EDIT: Actually, this solution comes with one fairly large flaw. I'm doing this behavior within a for-loop. So this prevents me from doing:

for string in lots_of_strings:
    for a in assign_match('(abc)(def)', string):
        do_my_work()
        continue # breaks out of this for loop instead of the parent
    other_work() # behavior i want to skip if the match is successful

Because the continue now breaks out of this loop instead of the parent for loop. If anyone has suggestions, I'd love to hear!

EDIT2: Alright, figured it out for real this time.

from contextlib import contextmanager
import re

@contextmanager
def assign_match(regex, string):
    match = re.match(regex, string)
    if match:
        yield match.groups()

for i in range(3):
    with assign_match('(abc)(def)', 'abcdef') as a:
#    for a in assign_match('(abc)(def)', 'abcdef'):
        print(a)
        continue
    print(i)

Sorry for the post - I swear I was feeling really stuck before I posted. :-) Hopefully someone else will find this interesting!

like image 116
Kurt Spindler Avatar answered Oct 10 '22 09:10

Kurt Spindler