Have you ever wanted to use switch-case statement in Python code? If yes, then you have probably found out that there is no such expression in native Python syntax. Fortunately, you can implement it on your own while having some nerd-level fun. This post explains how to implement switch-case statement in Python.
TL;DR
How to implement switch-case statement in Python, like this one:
var = 666
with switch(var) as case:
case(1, "foo")
case(2, "yolo")
case(3, "bar")
case.default("dunno")
result = case.result
print(result)
>>> "dunno"
Prerequisites & system requirements
- can-do attitude
- liberal approach to Python
- time for some nerd-fun
Switch-Case idea in Python
Here’s the idea: Python has with
statement, which can be used to control entering and exiting to the blocks of code. This is done by implementing __enter__
and __exit__
magic functions in your object.
Another magic function that is pretty handy is __call__
. This one allows to handle object “calling”, which is basically this:
x = MyClass()
x() # <-- __call__ method of object x of type MyClass will be executed there
OK - we have our building blocks. As you’ve probably saw in the TL;DR above, our desired syntax is the following:
with switch(SOME_OBJECT) as case:
case(1, "case value for 1")
case(2, "case value for 2")
case.default(r"¯\_(ツ)_/¯")
result = case.result
print(result)
Additionaly, I would like to have option for case condition and case result to be lambda
expression, like this:
y = object()
with switch(y) as case:
case(lambda: isinstance(y, str), "Some characters")
case(lambda: isinstance(y, int), "Numberzzzz")
case(lambda: isinstance(y, float), "Why u not double?")
case.default(r"¯\_(ツ)_/¯")
result = case.result
print(result)
Switch-Case implementation in Python
Without further ado, here’s the implementation:
class switch(object):
def __init__(self, object_to_switch_on):
self.object_to_switch_on = object_to_switch_on
self.default_result = None
self.result_store = None
self.cases = {}
self.evaluated = False
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
_ = self.result
def __call__(self, when, then):
if when in self.cases:
raise ValueError("This case already exists")
else:
self.cases[when] = then
return self
def default(self, then):
self.default_result = then
return self
@property
def result(self):
if not self.evaluated:
self.result_store = self.__evaluate__()
return self.result_store
def __evaluate__(self):
from inspect import signature
self.evaluated = True
result_tmp = None
if all(callable(x) for x in self.cases.keys()):
for case_fn, case_result in self.cases.items():
no_of_params = len(signature(case_fn).parameters)
if no_of_params >= 2:
raise ValueError("Case function must have either 0 or 1 arguments")
if no_of_params == 1 and case_fn(self.object_to_switch_on):
result_tmp = case_result
break
elif no_of_params == 0 and case_fn():
result_tmp = case_result
break
else:
result_tmp = self.default_result
elif all(not callable(x) for x in self.cases.keys()):
result_tmp = self.cases[self.object_to_switch_on] if self.object_to_switch_on in self.cases else self.default_result
else:
raise ValueError("Inconsistent switch usage, use either simple objects or functions")
if result_tmp is not None and callable(result_tmp):
return result_tmp()
else:
return result_tmp
Code explanation
- Class name is lowercased in order to imitate basic language syntax while used.
- Construtor (
__init__
) takes “switchable” object. - Magic functions:
__enter__
&__exit__
are here to implement Pythonwith
scope behaviour. Exit callsresult
property in order to trigger the evaluation when exiting the scope (it’s here just to shorten the usage syntax with lambdas even more, see exmaples below). - Magic function
__call__
is used to add cases to our switch as we call it. Cases are stored internally in a dictionary for fast direct object access (as long as the case is a hashable object). default
method adds default case and it’s optional to call it.result
property getter is triggering switch-case evaluation. If it’s called for the first time, it invokes__evaluate__
method, which does all the magic of resolving switch-case statement result. Every subsequent call returns the result from cache.__evaluate__
method is where all of the meat is. This method checks if all of the cases are either callable objects (most likely functions or lambdas). If they are, it iterates through them and assumes that match is first match for which the function/lambda returned truthy value. If none of the cases is callable, I assume that the objects are hashable and I just lookup for their values in the internal cases dictionary. After resolving the case result, I also check, whether result is callable - if it is, I invoke it, if not I return it.
Summary
And that’s it. Don’t rate the Pythonic code ratio. Like I said, it’s only for liberal Python programmes. Hope you liked it!
Additional links & resources
- full project on my github: https://github.com/marrrcin/python-switch-case-statement
Comments