Login [Register]
Don't have an account? Register now to chat, post, use our tools, and much more.
Yesterday I was porting some Java code over to Python in order to integrate two of my academic projects which have some shared utility. All was going smoothly, and I was marveling at how much more enjoyable Python is to write than Java until I ran into the issue of porting a couple switch statements that made good use of fall-through to save on code.

I quickly cursed Guido's foolishness, and moved on to the task of actually implementing a switch statement that has the important characteristics of its C/C++ brethren. Namely, implemented as a jump table rather than a series of comparisons, and supporting fall-through between cases (unlike some dictionary based versions I have seen).

Here is my preliminary switch class, with an example usage, to be followed up by an example of using it in a real application, and a discussion of what my next iteration will look like.


Code:
#!/usr/bin/env python

#############################################################
# Copyright by Thomas Dickerson under the terms of the LGPL #
#############################################################

cbreak = True
cpass = False

class Switch(object):
   
   def __init__(self):
      self.indices = {}
      self.case = {}
      self.lookup = {}
      self.default = lambda **kwargs : kwargs
         
         
   def __getitem__(self, key):
      return (self.case[key], self.indices)
      
   def __setitem__(self, key, value):
      key_count = len(self.case)
      self.case[key] = value
      
      self.indices[key] = key_count
      self.lookup[key_count] = key
      self.max = key_count + 1
      
   def __contains__(self, value):
      return value in case
   
   def __len__(self):
      return self.max
      
   
   def switch(self, val, **kwargs):
      self.index = self.indices[val] if val in self.indices else self.max
      for response, should_break in self:
         kwargs = response(**kwargs)
         if should_break: break
      else:
         kwargs = self.default(**kwargs)
      return kwargs
   
   def __iter__(self):
      return self
   
   def next(self):
      if self.index == self.max:
         raise StopIteration
      else:
         response, should_break = self.case[self.lookup[self.index]]
         self.index = self.max if should_break else (self.index + 1)
         return (response, should_break)


def main():
   a = 5
   b = 3
   test = Switch()
   def awesome(text):
      def mmk(**kwargs):
         print text
         return kwargs
      return mmk
   
   test['hi'] = (awesome("hi five"),cbreak)
   test['bye'] = (awesome('bye'),cpass)
   test['hmm'] = (awesome('hmm'),cbreak)
   test['what'] = (awesome('what'),cpass)
   test.default = awesome('default')
   
   print "switch 1"
   print test.switch('hi',**locals())
   print "switch 2"
   print test.switch("bye")
   print "switch 3"
   print test.switch("hmm")
   print "switch 4"
   print test.switch("what")
   print "default switch?"
   print test.switch("yo dawg")

if __name__ == '__main__':
   main()


Hopefully you'll have noticed a couple things about this code.
1) The switch object is reusable. The primary advantage of a switch construction over if/elif/else is the low overhead. This advantage would be negated if my implementation required that the switch object be re-initialized every time it is used. The difficulty of course is that Python is an interpreted language whereas most languages that support switch are compiled languages, and the compiler generates the jump table once. The result is that usage of my switch construction currently appears very different from a conventional switch statement since initialization and usage must be separate from each other in the code. This will become more clear in the next example.
2) There's a fair amount of boilerplate involved in generating the functions used as a actions for the switches in such a way that they can share local variables through keyword arguments. This is due largely to two things - first, that Python provides no easy way to inject variables into a scope less than the global scope; and second, that the switch definition is lexically separate from the switch invocation.


Code:

def init_prot_switch():
   def pface120(**kwargs):
      print "Face 120,\t",
      return kwargs
   
   def pplane90(**kwargs):
      print "Plane 90,\t",
      return kwargs
      
   def chain_funcs(*fns):
      def chained(**kwargs):
         for fn in fns:
            kwargs = fn(**kwargs)
         return kwargs
      return chained
   
   def palpha_gen(arm,angle):
      def palpha(**kwargs):
         print "Alpha%s %d,\t" % (arm, angle),
         return kwargs
      return palpha

   rotations = [[FACE240, FACE120],
             [PLANE270, PLANE180, PLANE90],
             [ALPHA_TETRADIHEDRAL, ALPHA_TETRAHEDRAL, ALPHA180],
             [ALPHAT_TETRADIHEDRAL, ALPHAT_TETRAHEDRAL]]
   
   responses = [[(pface120,cpass)]*2,
             [(pplane90,cpass)]*3,
             [(palpha_gen("",71),cbreak),
              (palpha_gen("",109),cbreak),
              (palpha_gen("",180),cbreak)],
             [(palpha_gen("2",71),cbreak),
              (palpha_gen("2",109),cbreak)]]
   
   cases = [zip(rotation_set,response_set)
            for rotation_set,response_set in zip(rotations,responses)]
   
   cases[2] += [(rotation | ALPHA180,
              (chain_funcs(palpha_gen("",180), response), should_break))
            for rotation, (response, should_break) in cases[2][:2]]
   
   switches = []
   for case in cases:
      switches.append(Switch())
      for rotation, switch_action in case:
         switches[-1][rotation] = switch_action
   return switches

print_rotation_switches = init_prot_switch()
def print_rotation(tracking):
   global print_rotation_switches
   for i,print_switch in enumerate(print_rotation_switches):
      print_switch.switch(tracking & (0xF000 >> (4*i)))
   print ""


I'm assuming some of you just winced, and some of you thought it was pretty nifty (or both). Amazingly, the code here is actually a little smaller than the Java code it was ported from, though it looks bulky due to the separation of definition and invocation. Hopefully you noticed that this code defines and uses, not one, but four switch statements.

I'm hoping to reduce the code footprint further (and fix some of the issues mentioned above) by investigating the use of decorators to bring the definition and invocation portions of my switch construction together. My understanding is that decorators are initialized at compile time (when the module is loaded) rather than at run time, so this should side-step both the lexical issues and the redefinition issues in one blow.

[edit]
Dang it, as I should have suspected, decorators of inner functions are initialized when the parent function is called, and not when it is defined (this makes sense since local/inner scopes don't exist until the function they belong to is invoked). This means I probably can't sidestep the lexical issue without losing the advantages of initialize-once. Thoughts? How would you go about making the usage more elegant? Which setup would you prefer (lexical convenience, or initialize-once)?
I've made some revisions today. I wasn't able to resolve the lexical scoping issues without requiring that the switch object be initialized before every invocation (but this can be done compatibly with the current state of the module, it simply detracts from the performance benefits of using a switch of if/elif/else), but I added some decorators to make things nice looking. The updated switches.py follows, and then a bit of further discussion of two equivalent examples.


Code:
#!/usr/bin/env python

#############################################################
# Copyright by Thomas Dickerson under the terms of the LGPL #
#############################################################

cbreak = True
cpass = False

class Case(object):
   
   def __init__(self, switch, case_id, should_break):
      self.switch = switch
      self.case_id = case_id
      self.should_break = should_break
      
   def __call__(self, f):
      def decorated_f(**kwargs):
         return f(**kwargs)
      self.switch[self.case_id] = (decorated_f, self.should_break)
      return decorated_f

class ParameterizedFn(object):
   def __init__(self, f):
      self.f = f
      
   def __call__(self, *args):
      def formatted_f(**kwargs):
         return self.f(*args, **kwargs)
      return formatted_f

def fn_chain(*fns):
   def chained(**kwargs):
      for fn in fns:
         kwargs = fn(**kwargs)
      return kwargs
   return chained



class Switch(object):
   
   def __init__(self):
      self.indices = {}
      self.cases = {}
      self.lookup = {}
      self.default_fn = lambda **kwargs : kwargs
      self.max = 0
         
   def case(self, case_id, should_break):
      return Case(self, case_id, should_break)
      
   def default(self, f):
      self.default_fn = f
      return self.default_fn
   
   def __getitem__(self, key):
      return (self.cases[key], self.indices)
      
   def __setitem__(self, key, value):
      self.cases[key] = value
      
      self.indices[key] = self.max
      self.lookup[self.max] = key
      self.max += 1
      
   def __contains__(self, value):
      return value in cases
   
   def __len__(self):
      return self.max
      
   
   def switch(self, val, **kwargs):
      self.index = self.indices[val] if val in self.indices else self.max
      for response, should_break in self:
         kwargs = response(**kwargs)
         if should_break: break
      else:
         kwargs = self.default_fn(**kwargs)
      return kwargs
   
   def __iter__(self):
      return self
   
   def next(self):
      if self.index == self.max:
         raise StopIteration
      else:
         response, should_break = self.cases[self.lookup[self.index]]
         self.index = self.max if should_break else (self.index + 1)
         return (response, should_break)


def main():
   a = 5
   b = 3
   test = Switch()
   @ParameterizedFn
   def mk(text, **kwargs):
      print text
      return kwargs
   
   
   @test.case("hi",cbreak)
   def fnHI(**kwargs):
      return mk("hi five")(**kwargs)
      
   @test.case("bye",cpass)
   def fnBYE(**kwargs):
      return mk("bye")(**kwargs)
   
   @test.case("hmm",cbreak)
   def fnHMM(**kwargs):
      return mk("hmm")(**kwargs)
      
   @test.case("what",cpass)
   def fnWHAT(**kwargs):
      return mk("what")(**kwargs)
      
   @test.default
   def fnDEFAULT(**kwargs):
      return mk("default")(**kwargs)
      
   print "switch 1"
   print test.switch('hi',**locals())
   print "switch 2"
   print test.switch("bye")
   print "switch 3"
   print test.switch("hmm")
   print "switch 4"
   print test.switch("what")
   print "default switch?"
   print test.switch("yo dawg")

if __name__ == '__main__':
   main()


The iffy looking demonstration from before has been implemented in two different ways, one mimicking a classic switch statement as closely as possible with decorators, and one using more typical python techniques with lists and list comprehensions and tuple unpacking to generate the switch constructions from a data structure.

The main function definition is still pretty much the same:

Code:
print_rotation_switches = init_prot_switch()
def print_rotation(tracking):
   global print_rotation_switches
   for i,print_switch in enumerate(print_rotation_switches):
      print_switch.switch(tracking & (0xF000 >> (4*i)))
   print ""


But the init_prot_switch() has been reworked to look like a "normal" switch (43 lines):

Code:
def init_prot_switch():
   def pface120(**kwargs):
      print "Face 120,\t",
      return kwargs
   
   def pplane90(**kwargs):
      print "Plane 90,\t",
      return kwargs
   
   @ParameterizedFn
   def palpha(arm, angle, **kwargs):
      print "Alpha%s %d,\t" % (arm, angle),
      return kwargs
   
   switch120 = Switch()
   @switch120.case(FACE240,cpass)
   def fnFACE240(**kwargs):
      return pface120(**kwargs)
      
   @switch120.case(FACE120,cpass)
   def fnFACE120(**kwargs):
      return pface120(**kwargs)
      
   switch90 = Switch()
   @switch90.case(PLANE270,cpass)
   def fnFACE270(**kwargs):
      return pplane90(**kwargs)
   
   @switch90.case(PLANE180, cpass)
   def fnFACE180(**kwargs):
      return pplane90(**kwargs)
   
   @switch90.case(PLANE90, cpass)
   def fnFACE90(**kwargs):
      return pplane90(**kwargs)
      
   switch_alpha = Switch()
   @switch_alpha.case(ALPHA_TETRADIHEDRAL, cbreak)
   def  fnALPHA_TETRADIHEDRAL(**kwargs):
      return palpha("",71)(**kwargs)
   
   @switch_alpha.case(ALPHA_TETRAHEDRAL, cbreak)
   def  fnALPHA_TETRAHEDRAL(**kwargs):
      return palpha("",109)(**kwargs)
   
   @switch_alpha.case(ALPHA180, cbreak)
   def  fnALPHA180(**kwargs):
      return palpha("",180)(**kwargs)
   
   @switch_alpha.case(ALPHA180 | ALPHA_TETRADIHEDRAL, cbreak)
   def  fnALPHA_180_PLUS_TETRADIHEDRAL(**kwargs):
      return fn_chain(fnALPHA180, fnALPHA_TETRADIHEDRAL)(**kwargs)
   
   @switch_alpha.case(ALPHA180 | ALPHA_TETRAHEDRAL, cbreak)
   def  fnALPHA_180_PLUS_TETRAHEDRAL(**kwargs):
      return fn_chain(fnALPHA180, fnALPHA_TETRAHEDRAL)(**kwargs)
      
   switch_alpha2 = Switch()
   @switch_alpha2.case(ALPHAT_TETRADIHEDRAL, cbreak)
   def  fnALPHAT_TETRADIHEDRAL(**kwargs):
      return palpha("2",71)(**kwargs)
   
   @switch_alpha2.case(ALPHAT_TETRAHEDRAL, cbreak)
   def  fnALPHAT_TETRAHEDRAL(**kwargs):
      return palpha("2",109)(**kwargs)
   
   return [switch120, switch90, switch_alpha, switch_alpha2]


and to generate them from Python data-structures (40 lines)

Code:
def init_prot_switch():
   def pface120(**kwargs):
      print "Face 120,\t",
      return kwargs
   
   def pplane90(**kwargs):
      print "Plane 90,\t",
      return kwargs
   
   @ParameterizedFn
   def palpha(arm, angle, **kwargs):
      print "Alpha%s %d,\t" % (arm, angle),
      return kwargs

   rotations = [[FACE240, FACE120],
             [PLANE270, PLANE180, PLANE90],
             [ALPHA_TETRADIHEDRAL, ALPHA_TETRAHEDRAL, ALPHA180],
             [ALPHAT_TETRADIHEDRAL, ALPHAT_TETRAHEDRAL]]
   
   responses = [[(pface120,cpass)]*2,
             [(pplane90,cpass)]*3,
             [(palpha("",71),cbreak),
              (palpha("",109),cbreak),
              (palpha("",180),cbreak)],
             [(palpha("2",71),cbreak),
              (palpha("2",109),cbreak)]]
   
   cases = [zip(rotation_set,response_set)
            for rotation_set,response_set in zip(rotations,responses)]
   
   cases[2] += [(rotation | ALPHA180,
              (fn_chain(palpha("",180), response), should_break))
            for rotation, (response, should_break) in cases[2][:2]]
   
   switches = []
   for case in cases:
      switches.append(Switch())
      for rotation, (response, should_break) in case:
         switches[-1].case(rotation, should_break)(response)
         
   return switches
This is looking good, the idea itself is cool, since Python lackes switches.

Concerning the code, not much to say about it, looking organized.

Nice work!
ScoutDavid wrote:
This is looking good, the idea itself is cool, since Python lackes switches.

Concerning the code, not much to say about it, looking organized.

Nice work!


Thanks on all counts =)

If anyone has suggestions to make the implementation more-Pythonesque without losing any of the abilities/advantages of having a switch in the first place, I'd love to hear them before I throw the module up on my server and call it a done deal.
elfprince13 wrote:
If anyone has suggestions to make the implementation more-Pythonesque without losing any of the abilities/advantages of having a switch in the first place, I'd love to hear them before I throw the module up on my server and call it a done deal.


It would help if you had more info about why exactly you needed a switch with fall through behavior. If you can say (or even show the Java code) what you are trying to accomplish I might be able to come up with something more Pythonesque.

Although I must confess I'm tempted to try and come up with a better switch hack Smile

elfprince13 wrote:
I quickly cursed Guido's foolishness


I actually agree with Guido - switches are ugly and wouldn't fit Python's syntax or design well at all. if/elif works well enough normally.
Kllrnohj wrote:
It would help if you had more info about why exactly you needed a switch with fall through behavior. If you can say (or even show the Java code) what you are trying to accomplish I might be able to come up with something more Pythonesque.

I can show a couple different examples where fall-through is useful, though I could turn all of it into something Pythonesque if I really wanted to translate instead of transliterate.

Some C:

Code:
for(i=0;i<length;i++){
      switch(inputBuffer[i]){
         case '\n':
            args[ct+1] = NULL;
         case ' ':
         case '\t':
            if(start+1){
               args[ct] = &inputBuffer[start];
               ct++;
            }
            inputBuffer[i] = '\0';
            start = -1;
            break;
         default:
            if(!(start+1)) start = i;
            if(inputBuffer[i] == '&'){
               *flag = 1;
               inputBuffer[i] = '\0';
            }
            
      }
   }
   args[ct] = NULL;
   return scanvars(args);


Some Java:

Code:
switch(rotTrack & 0xF000){
         case FACE240:
            System.out.print("Face 120,\t");
         case FACE120:
            System.out.print("Face 120,\t");
         default:
            break;
      }

      switch(rotTrack & 0x0F00){
         case PLANE270:
            System.out.print("Plane 90,\t");
         case PLANE180:
            System.out.print("Plane 90,\t");
         case PLANE90:
            System.out.print("Plane 90,\t");
         default:
            break;
      }


Both could be rewritten, probably without any conditionals at all.

Kllrnohj wrote:

Although I must confess I'm tempted to try and come up with a better switch hack Smile

I thought you'd never ask Razz

Kllrnohj wrote:

I actually agree with Guido - switches are ugly and wouldn't fit Python's syntax or design well at all. if/elif works well enough normally.
Bwahaha, prepare to have your mind BLOWN. I got fall through switches working in a super sexy way *including* preserving locals between cases (fuck I'm awesome). "locals" are preserved using some nifty global hackery and the inspect module.

First, example usage (note the class methods do *not* take a self argument):

Code:
@switch()
class foo:

    @case(1)
    def something():
        a = 'test'
        print('c1, continuing')
        switch_continue()

    @case(2)
    def weee():
        if 'a' in globals():
            b = a + 'ing'
        else:
            b = 'something else'
        print('c2, a = ' + b)
        switch_break()

    @case(3)
    def c3():
        print('c3')
        switch_break()

    @case(4)
    def c4():
        print('c4')

print('foo(1)')
foo(1)
print('foo(2)')
foo(2)
print('foo(3)')
foo(3)
print('foo(4)')
foo(4)
print('foo(5)')
foo(5)


Output:
Quote:
foo(1)
c1, continuing
c2, a = testing
foo(2)
c2, a = something else
foo(3)
c3
foo(4)
c4
Error! c4 didn't call break or continue!
foo(5)


Second sample, this time using self:

Code:
@switch(True)
class bar:
    retval = 0

    @case(1)
    def c1(self):
        self.retval = 10
        switch_continue()

    @case(2)
    def c2(self):
        self.retval += 20
        switch_break()

    @default
    def default(self):
        self.retval = 42


print('bar(1): ' + str(bar(1).retval))
print('bar(2): ' + str(bar(2).retval))
print('bar(3): ' + str(bar(3).retval))



Output:
Quote:
bar(1): 30
bar(2): 20
bar(3): 42



And here is the source. It could do with a bit of cleanup and some bundling up into a separate module to keep as much of it private as possible, but that's an exercise left to the reader:


Code:
import inspect
import itertools
import types

class CaseWrapper:
    __next_id = itertools.count().__next__

    def __init__(self, value, func):
        self.value = value
        self.func = func
        self.id = CaseWrapper.__next_id()

    def __repr__(self):
        return 'Case({0.value}): {0.func.__name__}'.format(self)

def case(value):
    def make_case(f):
        return CaseWrapper(value, f)
    return make_case

def default(f):
    return CaseWrapper(None, f)

def do_switch(value, cases, self=None):
    mlocals = globals().copy()
    do_continue = False
    clean_finish = False
    def switch_continue():
        nonlocal mlocals, do_continue, clean_finish
        caller = inspect.stack()[1][0]
        mlocals.update(caller.f_locals)
        do_continue = True
        clean_finish = True
    def switch_break():
        nonlocal mlocals, do_continue, clean_finish
        mlocals = globals().copy()
        do_continue = False
        clean_finish = True

    inject_locals = {'switch_continue': switch_continue,
                     'switch_break': switch_break}

    def run_case(c):
        nonlocal mlocals, clean_finish
        clean_finish = False
        mlocals.update(inject_locals)
        f = types.FunctionType(c.func.__code__, mlocals)
        if (self):
            f(self)
        else:
            f()

    found_case = False
    for c in cases:
        if c.value == value or do_continue:
            found_case = True
            run_case(c)
            if not clean_finish:
                print('Error! ' + c.func.__name__ + " didn't call break or continue!")
                return

    if not found_case:
        for c in cases:
            if c.value == None:
                run_case(c)
                return

def switch_init(self, value):
    cases = []
    for attr in dir(self):
        c = getattr(self, attr)
        if type(c) is CaseWrapper:
            cases.append(c)
    cases = sorted(cases, key=lambda case: case.id)
    if (self.__use_self):
        do_switch(value, cases, self)
    else:
        do_switch(value, cases)

def switch(use_self=False):
    def set_init(c):
        c.__init__ = switch_init
        c.__use_self = use_self
        return c
    return set_init
I definitely like this, and will spend some more time ogling after I get some sleep. I take it this is Python 3.x?

[edit]

Out of curiosity, how long did this keep you entertained?
elfprince13 wrote:
I definitely like this, and will spend some more time ogling after I get some sleep. I take it this is Python 3.x?


Yes, but nothing 3.x specific afaik.

Quote:
Out of curiosity, how long did this keep you entertained?


About 2 hours. There are some bugs with it I've noticed, some easily fixable, some not. For example, in the version posted the locals of the switch are not passed down, that is easily fixed (although I figure I'll let you try and tackle that one - if you get stuck let me know, otherwise it would be a good intro to the madness I created). However, propagating locals back out is pretty much impossible to do automatically. I'm thinking of just bundling up all the locals and setting it as a field on the switch class or something, not sure. I'll play around with that some more tomorrow.
Wow, that's pretty sweet. Nice work, guys.
Kllrnohj wrote:
Yes, but nothing 3.x specific afaik.

I didn't recognize the nonlocals keyword, and a quick googling turned up a lot of questions about when if/when it would be in Python 2.x.
Also the string formatting in your repr method.

Quote:
About 2 hours. There are some bugs with it I've noticed, some easily fixable, some not. For example, in the version posted the locals of the switch are not passed down, that is easily fixed (although I figure I'll let you try and tackle that one - if you get stuck let me know, otherwise it would be a good intro to the madness I created).


Meaning, for example that retval within class bar must be accessed with self.retval?

Quote:
However, propagating locals back out is pretty much impossible to do automatically.

That was one of the problems I ran into with locals, and why I ultimately decided to make copying in and out a bidirectional problem.


I like your implementation of switch_break/continue as function calls within each case rather than as arguments to the decorators - though I noticed it doesn't do implicit continues. A lot of people would probably see that as a feature though.
I feel like the real Pythonic way to do it would be something awkward with try/raise/except/finally. I've seen quite a few places where I expected something to be implemented with conditionals, and Python gurus suggested exceptional code structure.
The whole (expensive) "it's easier to ask forgiveness than permission" thing is dumb, man.
merthsoft wrote:
The whole (expensive) "it's easier to ask forgiveness than permission" thing is dumb, man.
I don't entirely agree with it myself, as much as I enjoy writing and using Python programs. However, it is indeed the Pythonic way to do things, as far as I can tell.
elfprince13 wrote:
Quote:
About 2 hours. There are some bugs with it I've noticed, some easily fixable, some not. For example, in the version posted the locals of the switch are not passed down, that is easily fixed (although I figure I'll let you try and tackle that one - if you get stuck let me know, otherwise it would be a good intro to the madness I created).


Meaning, for example that retval within class bar must be accessed with self.retval?


No, meaning that locals around the switch are not propagated to the cases. Meaning, the following will crash, but could easily be made to work (without changing the example, I mean, but just with changes to how switch and case are implemented):


Code:
@switch()
class foo:
  @case(1)
  def test():
    print('a = ' + repr(a))
    switch_break()

def main():
  a = 10
  foo(1)


Quote:
That was one of the problems I ran into with locals, and why I ultimately decided to make copying in and out a bidirectional problem.


I'm thinking I'll just set them on the class instance. So you could do something like this:


Code:
@switch()
class foo:
  @case(1)
  def c1():
    global a
    a += 10
    switch_break()

def test():
  a = 5
  f = foo(1)
  a = f['a']
  print('a = ' + repr(a))


And have the output be 'a = 15'

Quote:
though I noticed it doesn't do implicit continues. A lot of people would probably see that as a feature though.


The way it is implemented implicit continue is impossible - unless you don't care about propagating locals between switches. If instead you go the self route (@switch(True)), you could just set values to propagate between switches on the class instance via self and have implicit continue.
KermMartian wrote:
I feel like the real Pythonic way to do it would be something awkward with try/raise/except/finally. I've seen quite a few places where I expected something to be implemented with conditionals, and Python gurus suggested exceptional code structure.


Just curious, how would a case structure look with exceptions?
KermMartian wrote:
I feel like the real Pythonic way to do it would be something awkward with try/raise/except/finally. I've seen quite a few places where I expected something to be implemented with conditionals, and Python gurus suggested exceptional code structure.


I wonder if the way in which exception handling is implemented inside the Python interpreter would actually yield the performance benefits of a switch. Progranmatically generating unique exception types for cases would be possible, but I suspect the fall-through issues would still be present (unless raising a new exception within an except block would bump it down to a subsequent except block in the same series of try/except/finally). It would fix the scoping issues though.

Kllrnohj wrote:

No, meaning that locals around the switch are not propagated to the cases. Meaning, the following will crash, but could easily be made to work (without changing the example, I mean, but just with changes to how switch and case are implemented):


Code:
@switch()
class foo:
  @case(1)
  def test():
    print('a = ' + repr(a))
    switch_break()

def main():
  a = 10
  foo(1)

...

I'm thinking I'll just set them on the class instance. So you could do something like this:


Code:
@switch()
class foo:
  @case(1)
  def c1():
    global a
    a += 10
    switch_break()

def test():
  a = 5
  f = foo(1)
  a = f['a']
  print('a = ' + repr(a))


And have the output be 'a = 15'
...
The way it is implemented implicit continue is impossible - unless you don't care about propagating locals between switches. If instead you go the self route (@switch(True)), you could just set values to propagate between switches on the class instance via self and have implicit continue.

I suspect setting instance variables is the best way to do it for consistency between copy-in/copy-out. It would probably end up more verbose on the calling side than my **locals(), but it would be cleaner looking to access them from within the cases.
Pseudoprogrammer casts Necromancer. It's super effective!

I was at the Python Arkansas conference and some guy showed me some code where he hacked together a switch on the plane flight to the conference using super hacky try: except blocks with custom exceptions and whatnot. It ended up being like < 10 lines though. Sorry I don't have the code but I thought elfi and kllr might be interested.
Pseudoprogrammer wrote:
Pseudoprogrammer casts Necromancer. It's super effective!

I was at the Python Arkansas conference and some guy showed me some code where he hacked together a switch on the plane flight to the conference using super hacky try: except blocks with custom exceptions and whatnot. It ended up being like < 10 lines though. Sorry I don't have the code but I thought elfi and kllr might be interested.
Well, well, well, sounds like I was right after all, then!
KermMartian in June 2011 wrote:
I feel like the real Pythonic way to do it would be something awkward with try/raise/except/finally. I've seen quite a few places where I expected something to be implemented with conditionals, and Python gurus suggested exceptional code structure.
I don't know enough about the internals of how that's implemented to guess at the run time performance, but switches done right should be O(1) whereas conditionals are O(n) in the number of conditions. I wouldn't mind seeing an example though.
  
Register to Join the Conversation
Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.

» Go to Registration page
Page 1 of 2
» All times are GMT - 5 Hours
 
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

 

Advertisement