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:
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:
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 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)?