Skip to content

Instantly share code, notes, and snippets.

@trajing
Last active April 14, 2019 01:21
Show Gist options
  • Select an option

  • Save trajing/55bb832062f28c6a1da05736180d5673 to your computer and use it in GitHub Desktop.

Select an option

Save trajing/55bb832062f28c6a1da05736180d5673 to your computer and use it in GitHub Desktop.
Python switch/case Using Dynamic except

switch/case in Python

A manner of replicating the functionality of switch and case in other languages using variable type arguments to Python's except keyword.

Dynamic except

Python's except keyword takes as an argument either a type or tuple of types specifying what exceptions to capture (you can also bind the exception object to a variable, but we don't care about that right now.) it functions like so:

try:
    some_potentially_erroring_function()
except (Error1, Error2):
    handle_err1_err2()
except Error3:
    handle_err3()

This all seems very standard, until you realize that it takes types and tuples of types as an argument -- meaning that these can be dynamically produced at runtime. I could assign t = (Error1, Error2) and except t: in the preceding code snippet, and everything would function identically. Having found a small amount of dynamicism where I did not expect it, I felt compelled to create the most awful thing possible.

Usage

After importing the file, create a Switch object by calling it with predicates (callable objects returning booleans) which are then numbered (starting at 1, because they aren't members of an array -- they're the first case, second case, etc). Call its run method, passing in whatever arguments your predicates need, then for each case you wish to implement handling for, use switch.on_case -- use one argument to specify a singular case, two to specify an (inclusive) range of cases. Be sure to finish up with an except switch.end(): pass.

Notes

  • If you forget about a case, and forget to finish with an except switch.end(): pass, you will get a very interesting "InternalCaseHandlingException" crash.
  • on_case('else') only triggers when no other cases specified in the switch object trigger, rather than the cases listed in the switch statement. For this reason, I recommend not reusing switch objects.
  • You could probably make a ruby-like case using similar methodology, or even just deriving off of this code.
    • You can, and I just did (see the file 03-ruby-case.py).
  • If you really want case exceptions to be 0-indexed or use exclusive ranges, all the logic for that is all in on_case.
  • Don't use this.
class Switch:
def __init__(self, *args):
self.predicates = args
self.exceptions = []
for predicate in args:
self.exceptions.append(type("InternalCaseHandlingException", (Exception,), {}))
self.else_exception = type("InternalCaseHandlingException", (Exception,), {})
def run(self, *args):
for (predicate, exception) in zip(self.predicates, self.exceptions):
if predicate(*args): raise exception
raise self.else_exception
def on_case(self, *args):
# amusingly, this is a case where `case/switch` would make my code clearer
if len(args) == 1:
case = args[0]
if case == 'else':
return self.else_exception
return self.exceptions[case - 1]
elif len(args) == 2:
start = args[0]
end = args[1]
return tuple(self.exceptions[(start - 1):(end)])
else:
raise TypeError('on_case() takes 1 or 2 arguments')
def end(self):
return tuple(self.exceptions + [self.else_exception])
if __name__ == '__main__':
switch = Switch(lambda x: x == 'q',
lambda x: x == 'r',
lambda x: x == 'p',
lambda x: x == 'z')
try:
switch.run(input("? "))
except switch.on_case(1):
print('quit')
except switch.on_case(2,3):
print('run or print')
except switch.on_case('else'):
print('unrecognized command')
except switch.end(): pass
class RubyCasePredicate:
def __init__(self, obj):
self.obj = obj
def __call__(self, *args):
return self.obj.case_equal(*args)
def RubySwitch(*args):
return Switch(*map(RubyCasePredicate, args))
if __name__ == '__main__':
class CaseString(str):
def case_equal(self, x): return self == x
# should be equivalent to other example
switch = RubySwitch(*map(CaseString, ('q','r','p','z')))
try:
switch.run(input("? "))
except switch.on_case(1):
print('quit')
except switch.on_case(2,3):
print('run or print')
except switch.on_case('else'):
print('unrecognized command')
except switch.end(): pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment