Supposing we have a class as follows:
class PersonalChef():
def cook():
print("cooking something...")
And we want what it cooks to be a function of the time of day, we could do something like this:
class PersonalChef():
def cook(time_of_day):
## a few ways to do this, but this is quite concise:
meal = {'morning':'breakfast', 'midday':'lunch', 'evening':'dinner'}[time_of_day]
print("Cooking", meal)
PersonalChef().cook('morning')
>>> Cooking breakfast
A potentially nice syntax form for this would be using decorators. With some under-the-hood machinery buried inside at_time
, it ought to be possible to get it to work like this:
class PersonalChef():
@at_time('morning')
def cook():
print("Cooking breakfast")
@at_time('midday')
def cook():
print("Cooking lunch")
@at_time('evening')
def cook():
print("Cooking dinner")
PersonalChef().cook('morning')
>>> Cooking breakfast
The reason this could be a nice syntax form is shown by how it then shows up in subclasses:
class PersonalChefUK(PersonalChef):
@at_time('evening')
def cook():
print("Cooking supper")
The code written at the sub-class level is extremely minimal and doesn't require any awareness of the base-class implementation/data structures and doesn't require any calls to super() to pass-through the other scenarios. So it could be nice in a situation where there are a large number of people writing derived-classes for whom we want to pack and hide complexity away in the base class and make it hard for them to break the functionality.
However, I've tried a few different ways of implementing this and gotten stuck. I'm quite new to decorators, though, so probably missing something important. Any suggestions/comments?
A simpler approach would be to create a decorator factory that stores the decorated function in a dict that maps the function name and time of day to the function object, and returns a wrapper function that calls the stored function in the first class in the MRO that has it defined for the given time of day:
def at_time(time_of_day, _actions={}):
def decorator(func):
def wrapper(self, time_of_day):
for cls in type(self).__mro__:
if func := _actions.get((f'{cls.__qualname__}.{name}', time_of_day)):
return func(self)
raise ValueError(f'No {name} found in the {time_of_day} time')
name = func.__name__
_actions[func.__qualname__, time_of_day] = func
return wrapper
return decorator
so that:
class PersonalChef():
@at_time('morning')
def cook(self):
print("Cooking breakfast")
@at_time('evening')
def cook(self):
print("Cooking dinner")
class PersonalChefUK(PersonalChef):
@at_time('evening')
def cook(self):
print("Cooking supper")
PersonalChef().cook('morning')
PersonalChef().cook('evening')
PersonalChefUK().cook('morning')
PersonalChefUK().cook('evening')
outputs:
Cooking breakfast
Cooking dinner
Cooking breakfast
Cooking supper