Skip to content

Instantly share code, notes, and snippets.

@mikeckennedy
Last active August 21, 2019 04:55
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mikeckennedy/23ebca9ee1afd29f4534539d0ad3419d to your computer and use it in GitHub Desktop.
Save mikeckennedy/23ebca9ee1afd29f4534539d0ad3419d to your computer and use it in GitHub Desktop.
Could we easily add switch to the Python language? I think the answer is maybe yes!
# Here is an example of some syntax I'm proposing:
# See the github repo at https://github.com/mikeckennedy/python-switch
def test_switch():
num = 7
val = input("Enter a key. a, b, c or any other: ")
with Switch(val) as s:
s.case('a', process_a)
s.case('b', process_b)
s.case('c', lambda: process_with_data(val, num, 'other values still'))
s.default(process_any)
# process_a, process_b, and process_any are simple void/void methods
def process_a():
print("Found A!")
def process_b():
print("Found B!")
def process_any():
print("Found Default!")
def process_with_data(*value):
print("Found with data: {}".format(value))
# Here is a first pass implementation at adding switch
class Switch:
def __init__(self, value):
self.value = value
self.cases = {}
def default(self, func: Callable[[], None]):
self.case('__default__', func)
def case(self, key, func: Callable[[], None]):
if key in self.cases:
raise ValueError("Duplicate case: {}".format(key))
if not func:
raise ValueError("Action for case cannot be None.")
self.cases[key] = func
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
func = self.cases.get(self.value)
if not func:
func = self.cases.get('__default__')
if not func:
raise Exception("Value does not match any case and there is no default case: value {}".format(self.value))
func()
@marksweiss
Copy link

First, big fan of the show! Now that we got that out of the way I think the approach of only passing callables somewhat defeats the purpose of the main use case for switch in C, C++ and Ruby (and, really, C, where this originated), which is to have all the branches present in short block scopes right there in the switch statement, below each switch condition.

There are some semantics that are implicit but strong enough for even moderately experienced to sense when switch makes sense. Basically, I would argue, the core switch use case is:

  • a series of branches based on testing a variable that can have a small domain of values or small number of relevant ranges of values
  • a small block of code to be executed for each branch

The first bullet means this can be a better choice (for clarity) than if/else, because the conditions are more clearly related and more concisely written. OTOH if you have more complex branching tests you need if/else. The second bullet, I'm arguing, is the raison d'etre of this construct.

I like what you are trying to do, but right now it's too close to syntactic sugar over branching + function calls.

@rbcollins123
Copy link

I tend to agree with @marksweiss... I also think it's easier when new to Python to grasp "there is no switch, use if/elif instead" than to have to embrace a flavor of switch that uses only functions and the necessary boilerplate around each switch use.

@mikeckennedy
Copy link
Author

Hi guys, thanks for the kind words and thoughts on this. Have a look here and let me know if you still feel that way:

https://github.com/mikeckennedy/python-switch#why-not-just-raw-dict

@pylang
Copy link

pylang commented Sep 14, 2017

Not sure where to post this thought ...

You know, switch/cases and singledispatch have something in common. The first can say "execute this function for a given value." The second says "execute this function for a given type".

It seems you could adapt your code to dispatch for certain types as well, e.g.:

with Switch(val) as s:
    s.case([int, float], process_a)
    s.case(list, process_b)

Thanks for this contrib.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment