This is self-explaining example with usage in doctests (it's not that fast as implementation with dict key-lookups, but it's a lot more readable, and don't require callables and lambdas):
class Switch(object):
"""
Switch, simple implementation of switch statement for Python, eg:
>>> def test_switch(val):
... ret = []
... with Switch(val) as case:
... if case(1, fall_through=True):
... ret.append(1)
... if case(2):
... ret.append(2)
... if case.call(lambda v: 2 < v < 4):
... ret.append(3)
... if case.call(lambda v: 3 < v < 5, fall_through=True):
... ret.append(4)
... if case(5):
... ret.append(5)
... if case.default:
... ret.append(6)
... return ret
...
>>> test_switch(1)
[1, 2]
>>> test_switch(2)
[2]
>>> test_switch(3)
[3]
>>> test_switch(4)
[4, 5]
>>> test_switch(5)
[5]
>>> test_switch(7)
[6]
>>> def test_switch_default_fall_through(val):
... ret = []
... with Switch(val, fall_through=True) as case:
... if case(1):
... ret.append(1)
... if case(2):
... ret.append(2)
... if case.call(lambda v: 2 < v < 4):
... ret.append(3)
... if case.call(lambda v: 3 < v < 5, fall_through=False):
... ret.append(4)
... if case(5):
... ret.append(5)
... if case.default:
... ret.append(6)
... return ret
...
>>> test_switch_default_fall_through(1)
[1, 2, 3, 4]
>>> test_switch_default_fall_through(2)
[2, 3, 4]
>>> test_switch_default_fall_through(3)
[3, 4]
>>> test_switch_default_fall_through(4)
[4]
>>> test_switch_default_fall_through(5)
[5]
>>> test_switch_default_fall_through(7)
[6]
"""
class StopExecution(Exception):
pass
def __init__(self, test_value, fall_through=False):
self._value = test_value
self._fall_through = None
self._default_fall_through = fall_through
self._use_default = True
self._default_used = False
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is self.StopExecution:
return True
return False
def __call__(self, expr, fall_through=None):
return self.call(lambda v: v == expr, fall_through)
def call(self, call, fall_through=None):
if self._default_used:
raise SyntaxError('Case after default is prohibited')
if self._finished:
raise self.StopExecution()
elif call(self._value) or self._fall_through:
self._use_default = False
if fall_through is None:
self._fall_through = self._default_fall_through
else:
self._fall_through = fall_through
return True
return False
@property
def default(self):
if self._finished:
raise self.StopExecution()
self._default_used = True
if self._use_default:
return True
return False
@property
def _finished(self):
return self._use_default is False and self._fall_through is False
class CSwitch(Switch):
"""
CSwitch is a shortcut to call Switch(test_value, fall_through=True)
"""
def __init__(self, test_value):
super(CSwitch, self).__init__(test_value, fall_through=True)
1 Answer 1
I like your trick to create this syntactic sugar. The implementation is also pretty good, as are the doctests.
Feature suggestions
I think it would be nice if a case()
could test for multiple values. A case('jack', 'queen', 'king')
should match if the Switch
was created with any of those three strings.
It would also be nice if there were a case.match()
that performed a regular expression match.
Minor issues
If execution ends up inside
case.call()
due to fall-through, then I would expect the test function not to be called at all, as a kind of short-circuiting behaviour. Specifically,elif call(self._value) or self._fall_through:
should be reversed and written as
elif self._fall_through or call(self._value):
Having a parameter named
call
when the method is also namedcall
is confusing. I suggest renaming the parameter totest
.Avoid testing variables for equality with
True
andFalse
explicitly. Just use boolean expressions. For example, in__exit__()
, changedef __exit__(exc_type, exc_val, exc_tb): if exc_type is self.StopExecution: return True return False
to
def __exit__(exc_type, exc_val, exc_tb): return exc_type is self.StopExecution
Initialize
_fall_through
toFalse
instead ofNone
; it's slightly more informative.Rename
expr
→case_value
. Rename_value
→_switch_value
.Rename / invert
_use_default
tonot _matched_case
, because_use_default
is too confusingly similar to_default_used
. Also, by inverting the logic, all three private variables can be initialized toFalse
, which is more elegant.Instead of a
_finished
property, write a_check_finished()
method that raisesStopException
too.
Proposed solution
import re
class Switch(object):
"""
Switch, simple implementation of switch statement for Python, eg:
>>> def test_switch(val):
... ret = []
... with Switch(val) as case:
... if case(1, fall_through=True):
... ret.append(1)
... if case.match('2|two'):
... ret.append(2)
... if case.call(lambda v: 2 < v < 4):
... ret.append(3)
... if case.call(lambda v: 3 < v < 5, fall_through=True):
... ret.append(4)
... if case(5, 10):
... ret.append('5 or 10')
... if case.default:
... ret.append(6)
... return ret
...
>>> test_switch(1)
[1, 2]
>>> test_switch(2)
[2]
>>> test_switch(3)
[3]
>>> test_switch(4)
[4, '5 or 10']
>>> test_switch(5)
['5 or 10']
>>> test_switch(10)
['5 or 10']
>>> test_switch(7)
[6]
>>> def test_switch_default_fall_through(val):
... ret = []
... with Switch(val, fall_through=True) as case:
... if case(1):
... ret.append(1)
... if case(2):
... ret.append(2)
... if case.call(lambda v: 2 < v < 4):
... ret.append(3)
... if case.call(lambda v: 3 < v < 5, fall_through=False):
... ret.append(4)
... if case(5):
... ret.append(5)
... if case.default:
... ret.append(6)
... return ret
...
>>> test_switch_default_fall_through(1)
[1, 2, 3, 4]
>>> test_switch_default_fall_through(2)
[2, 3, 4]
>>> test_switch_default_fall_through(3)
[3, 4]
>>> test_switch_default_fall_through(4)
[4]
>>> test_switch_default_fall_through(5)
[5]
>>> test_switch_default_fall_through(7)
[6]
"""
class StopExecution(Exception):
pass
def __init__(self, switch_value, fall_through=False):
self._switch_value = switch_value
self._default_fall_through = fall_through
self._fall_through = False
self._matched_case = False
self._default_used = False
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
return exc_type is self.StopExecution
def __call__(self, case_value, *case_values, **kwargs):
def test(switch_value):
return any(switch_value == v for v in (case_value,) + case_values)
return self.call(test, **kwargs)
def call(self, test, fall_through=None):
if self._default_used:
raise SyntaxError('Case after default is prohibited')
self._check_finished()
if self._fall_through or test(self._switch_value):
self._matched_case = True
self._fall_through = fall_through if fall_through is not None else self._default_fall_through
return True
return False
def match(self, regex, fall_through=None):
if self._default_used:
raise SyntaxError('Match after default is prohibited')
self._check_finished()
if isinstance(regex, str):
regex = re.compile(regex)
if self._fall_through or regex.match(str(self._switch_value)):
self._matched_case = True
self._fall_through = fall_through if fall_through is not None else self._default_fall_through
return True
return False
@property
def default(self):
self._check_finished()
self._default_used = True
return not self._matched_case
def _check_finished(self):
if self._matched_case and not self._fall_through:
raise self.StopExecution()
class CSwitch(Switch):
"""
CSwitch is a shortcut to call Switch(switch_value, fall_through=True)
"""
def __init__(self, switch_value):
super(CSwitch, self).__init__(switch_value, fall_through=True)
-
\$\begingroup\$ Thanks, I've added also ability to have multiple regexp patterns within single
case/match
call, code is available on pypi: pypi.python.org/pypi/switch/1.1.0 \$\endgroup\$canni– canni2014年04月12日 11:52:01 +00:00Commented Apr 12, 2014 at 11:52
case/break
syntax of C, here is an interesting implementation: code.activestate.com/recipes/410692. \$\endgroup\$