how can I combine a switch-case and regex in Python
Asked Answered
S

6

7

I want to process a string by matching it with a sequence of regular expression. As I'm trying to avoid nested if-then, I'm thinking of switch-case. How can I write the following structure in Python? Thank you

switch str:
   case match(regex1):
       # do something
   case match(regex2):
       # do sth else

I know Perl allows one to do that. Does Python?

Sonnier answered 12/2, 2011 at 19:35 Comment(2)
This is not legal syntax in Perl. Do you mean given / when syntax?Panegyrize
@drewk: yes, that's what I want.Sonnier
P
1

You are looking for pyswitch (disclaimer: I am the author). With it, you can do the following, which is pretty close to the example you gave in your question:

from pyswitch import Switch

mySwitch = Switch()

@myswitch.caseRegEx(regex1)
def doSomething(matchObj, *args, **kwargs):
    # Do Something
    return 1

@myswitch.caseRegEx(regex2)
def doSomethingElse(matchObj, *args, **kwargs):
    # Do Something Else
    return 2

rval = myswitch(stringYouWantToSwitchOn)

There's a much more comprehensive example given at the URL I linked. pyswitch is not restricted to just switching on regular expressions. Internally, pyswitch uses a dispatch system similar to the examples others have given above. I just got tired of having to re-write the same code framework over and over every time I needed that kind of dispatch system, so I wrote pyswitch.

Paginal answered 13/2, 2011 at 0:43 Comment(0)
S
9

First consider why there is no case statement in Python. So reset you brain and forget them.

You can use an object class, function decorators or use function dictionaries to achieve the same or better results.

Here is a quick trivial example:

#!/usr/bin/env python
import re

def hat(found):
    if found: print "found a hat"
    else: print "no hat"

def cat(found):
    if found: print "found a cat"
    else: print "no cat"

def dog(found):    
    if found: print "found a dog"
    else: print "no dog"

st="""
Here is the target string
with a hat and a cat
no d o g 
end
"""

patterns=['hat', 'cat', 'dog']
functions=[hat,cat,dog]

for pattern,case in zip(patterns,functions):
    print "pattern=",pattern
    case(re.search(pattern,st))

C style case / switch statements also "fall through, such as:

   switch(c) {
       case 'a':
       case 'b':
       case 'c':  do_abc();
                  break;
       ... other cases...
   }

Using tuples and lists of callables, you can get the similar behavior:

st="rat kitten snake puppy bug child"

def proc1(st): print "cuddle the %s" % st
def proc2(st): print "kill the %s" % st
def proc3(st): print "pick-up the %s" % st
def proc4(st): print "wear the %s" % st
def proc5(st): print "dispose of the %s" %st
def default(st): print "%s not found" % st

dproc={ ('puppy','kitten','child'): 
            [proc3, proc1], 
        ('hat','gloves'): 
            [proc3, proc4], 
        ('rat','snake','bug'): 
            [proc2, proc3, proc5]}

for patterns,cases in dproc.iteritems():
    for pattern in patterns:
        if re.search(pattern,st):
            for case in cases: case(pattern)
        else: default(pattern)    
        print

This gets the order for the found item correct: 1) pick up child, cuddle the child; 2) kill the rat, pick up the rat... It would be difficult to do the same with a C switch statement in an understandable syntax.

There are many other ways to imitate a C switch statement. Here is one (for integers) using function decorators:

case = {}

def switch_on(*values):
    def case_func(f):
        case.update((v, f) for v in values)
        return f
    return case_func

@switch_on(0, 3, 5)
def case_a(): print "case A"

@switch_on(1,2,4)
def case_b(): print "case B"

def default(): print "default"

for i in (0,2,3,5,22):
    print "Case: %i" % i
    try: 
        case[i]()
    except KeyError:
        default()

To paraphrase Larry Wall, Tom Christiansen, Jon Orwant in Programming Perl regarding understanding context in Perl:

You will be miserable programming Python until you use the idioms that are native to the language...

Surfboard answered 12/2, 2011 at 21:2 Comment(0)
T
5

A quick search shows a similar question asked earlier with multiple work arounds. May favorite solution from that one is by Mizard

import re

class Re(object):
  def __init__(self):
    self.last_match = None
  def match(self,pattern,text):
    self.last_match = re.match(pattern,text)
    return self.last_match
  def search(self,pattern,text):
    self.last_match = re.search(pattern,text)
    return self.last_match

gre = Re()
if gre.match(r'foo',text):
  # do something with gre.last_match
elif gre.match(r'bar',text):
  # do something with gre.last_match
else:
  # do something else
Techno answered 12/2, 2011 at 19:39 Comment(0)
M
1

If you're avoiding if-then, you can build on something like this:

import re

# The patterns
r1 = "spam"
r2 = "eggs"
r3 = "fish"

def doSomething1():
    return "Matched spam."

def doSomething2():
    return "Matched eggs."

def doSomething3():
    return "Matched fish."

def default():
    return "No match."

def match(r, s):
    mo = re.match(r, s)
    try:
        return mo.group()
    except AttributeError:
        return None

def delegate(s):
    try:
        action = {
            match(r1, s): doSomething1,
            match(r2, s): doSomething2,
            match(r3, s): doSomething3,
        }[s]()
        return action
    except KeyError:
        return default()

Results

>>> delegate("CantBeFound")
0: 'No match.'
>>> delegate("spam")
1: 'Matched spam.'
>>> delegate("eggs")
2: 'Matched eggs.'
>>> delegate("fish")
3: 'Matched fish.'
Mcfadden answered 12/2, 2011 at 22:42 Comment(0)
P
1

You are looking for pyswitch (disclaimer: I am the author). With it, you can do the following, which is pretty close to the example you gave in your question:

from pyswitch import Switch

mySwitch = Switch()

@myswitch.caseRegEx(regex1)
def doSomething(matchObj, *args, **kwargs):
    # Do Something
    return 1

@myswitch.caseRegEx(regex2)
def doSomethingElse(matchObj, *args, **kwargs):
    # Do Something Else
    return 2

rval = myswitch(stringYouWantToSwitchOn)

There's a much more comprehensive example given at the URL I linked. pyswitch is not restricted to just switching on regular expressions. Internally, pyswitch uses a dispatch system similar to the examples others have given above. I just got tired of having to re-write the same code framework over and over every time I needed that kind of dispatch system, so I wrote pyswitch.

Paginal answered 13/2, 2011 at 0:43 Comment(0)
I
1

Python since version 3.10 supports switch statements. Here's an example using regex with Python's new switch statement synax:

match = re.match(r"(?P<time>\d+)(?P<unit>[a-zA-Z]{1,2})", duration_string)
time = int(match.group("time"))
unit = match.group("unit")

match unit:
    case "us":
        return timedelta(microseconds=time)
    case "ms":
        return timedelta(milliseconds=time)
    case "s":
        return timedelta(seconds=time)
    case "m":
        return timedelta(minutes=time)
    case "h":
        return timedelta(hours=time)
    case "d":
        return timedelta(days=time)
    case "w":
        return timedelta(weeks=time)
    case _:
        raise TypeError("Unsupported time duration.")

This will take a string with a number and a time unit in it, e.g. "15d" is 15 days. The regex splits the 15 and the d up, then a switch statement is performed on the units.

This code converts Polar's style time duration format (further information here) to the timedelta format.

Of course you can use any kind of regex with a switch statement like this. I recommend named groups, as it makes reading the code easier.

Inartificial answered 1/8 at 12:33 Comment(0)
P
0

Your question regarding Perl style switch statements is ambiguous. You reference Perl but you are using a C style switch statement in your example. (There is a deprecated module that provides C style switch statements in Perl, but this is not recommended...)

If you mean Perl given / when type switch statements, this would not be trivial to implement in Python. You would need to implement smart matching and other non-trivial Perl idioms. You might as well just write whatever in Perl?

If you mean C style switch statements, these are relatively trivial in comparison. Most recommend using a dictionary dispatch method, such as:

import re

def case_1():
    print "case 1"
    return 1

def case_2():
    print "case 2"
    return 2

def case_3():
    print "case 3"
    return 3

def default():
    print "None"
    return 0

dispatch=   {  
            'a':            case_1, 
            'g':            case_2, 
            'some_other':   case_3,
            'default':      default
            }

str="abcdefg"

r=[dispatch[x]() if re.search(x,str) else dispatch['default']() 
             for x in ['a','g','z'] ]

print "r=",r
Panegyrize answered 12/2, 2011 at 22:37 Comment(1)
I am going to use lambdas here to make the expression of the cases more declarative. Thanks for the inspiration!Flighty

© 2022 - 2024 — McMap. All rights reserved.