There is a project at work where I needed to do the same action on various functions. It's basically a checkpoint system where I'd write some state to a file, but only when all the functionality in the given functions executed successfully. It was a one-line change but I thought it was a good excuse to create decorators for the first time in my life.
Here is an example of a decorator function:
def checkpoint(function):
wrapper(*args, **kwargs):
with open(PATH) as f:
for line in f:
if function.__name__ in line:
return
value = function(*args, **kwargs)
with open(PATH, 'a+') as f:
f.write(function.__name__)
return value
return wrapper
All it does is write some text to a file, and avoid running whatever function will be decorated by it if there is a match in that file. This would be an indicator that the function had already been executed (in a previous run).
The following snippet sees the use of this decorator:
@checkpoint
def do_this(some_argument):
# exit(1) on error
...
@checkpoint
def do_that(some_other_argument, some_optional_argument=None):
# exit(1) on error
...
if __name__ == '__main__':
do_this('some value')
do_that('some other value')
Without the decorator syntax, the same functionality would be achieved with:
def do_this(some_argument):
# exit(1) on error
...
do_this = checkpoint(do_this)
def do_that(some_other_argument, some_optional_argument=None):
# exit(1) on error
...
do_that = checkpoint(do_that)
if __name__ == '__main__':
do_this('some value')
do_that('some other value')
The latter format, though it presents less of a cognitive burden, feels less of an obvious solution, and it's a bit uglier as well. PEP 380 is a detailed discussion written over 10 years ago when the syntax was first added to the language.