Yes I know context managers are a much better way to do this, but I spent four hours working on implementing a switch construct using generic decorators, and I'd like to hear feedback on how I did. I'll do it with context managers tomorrow.
I don't think I qualify as a beginner anymore (I'm looking to start getting paid to write Python soon), and I would appreciate guidance on best practices.
My Code
"""Module to implement C style switch functionality using decorators.
Functions:
`switch_construct`: Returns functions that provide the required functionality and provides a closure for said functions.
"""
__all__ = ["switch", "case", "break_"]
import operator as op
def switch_construct():
"""Forms a closure to store the meta data used by the switch construct.
Vars:
`closure` (list): List to store dictionaries containing metadata for each `switch` instance.
* The dictionary contains two keys: "control" and "halt".
- "control": the value governing the current switch instance. `case` blocks are tested against this value.
- `halt`: value is used to implement break functionality.
> `True`: break after execution of current function.
> Default: `False`.
Returns:
tuple: A tuple of functions (`switch`, `case`, `break_`) defined in the function's scope.
"""
closure = []
def switch(input_):
"""Sets the metadata for the current `switch` block.
Args:
`input` (object): The value governing the switch construct.
Returns:
`function`: A decorator for the function enclosing the switch block.
"""
nonlocal closure
def decorator(func):
closure.append({"control": input_, "halt": False}) # Add the metadata to closure.
# Sets `control` to `input_` and `halt` to `False` for each `switch` block.
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
wrapper.switch_index = len(closure)-1
# Assign the `switch_index` attribute to the decorated function for using when retrieving switch construct metadata.
return wrapper
return decorator
def case(value, enclosing, test = op.eq):
"""Returns a decorator which executes decorated function only if its `value` argument satisfies some test.
Args:
`value` (object): value used to test whether the decorated function should be executed.
`enclosing` (function): enclosing function corresponding to the current switch block.
`test` (function): test that should be satisfied before decorated function is executed.
- Default value is `op.eq`.
- Returns `bool`.
Returns:
function: A decorator for the function corresponding to the case block.
"""
def decorator(func):
nonlocal closure
index = enclosing.switch_index
return_value = None
if test(value, closure[index]["control"]) and not closure[index]["halt"]:
return_value = func()
return return_value
return decorator
def break_(enclosing):
"""Used to indicate that the switch construct should break after execution of the current function."""
nonlocal closure
closure[enclosing.switch_index]["halt"] = True
return None
return case, switch, break_
case, switch, break_ = switch_construct()
@switch(2)
def func():
"""Sample use case of the switch construct."""
@case(1, func)
def _(num=10):
print(num + 1)
@case(2, func)
def _(num=10):
print(num + 2)
break_(func)
@case(3, func, op.gt)
def _(num=10):
print(num + 3)
func()