fictive.patterns.decorators module¶
Common patterns for working with decorators
Functions
-
fictive.patterns.decorators.optional_arguments(argumented_decorator)[source]¶ decroate a decorator to be used with or without arguments
Some decorators are used without additional arguments, e.g.:
import functools def shout(function): suffix = '!' @functools.wraps(function) def decorated(*args, **kwargs): return function(*args, **kwargs).upper() + suffix return decorated @shout def say_hi(name): return f"Hi, {name}"
>>> say_hi('adam') 'HI, ADAM!'
Other decorators are used with additional arguments that can control characteristics of the decorator. E.g.:
import functools def shout(volume=1): def controlled_shout(function): suffix = '!' * volume @functools.wraps(function) def decorated(*args, **kwargs): return function(*args, **kwargs).upper() + suffix return decorated return controlled_shout @shout(1) def say_ok(name): return f"Ok, {name}" @shout(3) def say_watch_out(name): return f"Watch out, {name}"
>>> say_ok('adam') 'OK, ADAM!' >>> say_watch_out('adam') 'WATCH OUT, ADAM!!!'
As above, the decorator may take optional keyword arguments or provide suitable default options. In that the case, the syntax would still require empty parentheses:
@shout() def say_goodbye(name): return f"Goodbye, {name}"
While functional, this can look a bit non-idiomatic in practice. Furthermore, if one inadvertently neglects to add the
(), this might not be caught as a syntax error, but will likely lead to anExceptionwhen calling the decorated object:@shout def say_welcome(name): return f"Welcome, {name}"
>>> say_welcome('adam') Traceback (most recent call last): ... TypeError: can't multiply sequence by non-int of type 'function'
This
optional_argumentsdecorator can decorate other decorators so that they can be used with or without the argument parentheses syntax. E.g.:import functools from fictive.patterns.decorators import optional_arguments @optional_arguments def shout(volume=1): def controlled_shout(function): suffix = '!' * volume @functools.wraps(function) def decorated(*args, **kwargs): return function(*args, **kwargs).upper() + suffix return decorated return controlled_shout @shout def say_ok(name): return f"Ok, {name}" @shout(3) def say_watch_out(name): return f"Watch out, {name}"
>>> say_ok('adam') 'OK, ADAM!' >>> say_watch_out('adam') 'WATCH OUT, ADAM!!!'
- Parameters
argumented_decorator (
callable) – this should be a decorator that can take one or more optional arguments. I.e,argumented_decorratorshould be useable asargumented_decorator()(function)- Returns
a new decorator that can be used with or without the parentheses syntax.
NOTE: if the ONLY argument to the
argumented_decoratoris acallable, it MUST be provided as a keyword argument when using the new decorator. E.g:import functools from fictive.patterns.decorators import optional_arguments @optional_arguments def modulate(transform=str.upper): def controlled_say(function): @functools.wraps(function) def decorated(*args, **kwargs): return transform(function(*args, **kwargs)) return decorated return controlled_say @modulate def say_hi(name): return f"Hi, {name}" @modulate(transform=str.title) def say_good_day(name): return f"Good day, {name}"
>>> say_hi('adam') 'HI, ADAM' >>> say_good_day('adam') 'Good Day, Adam'
If the only argument is a
callableand it is used as a positional argument, the new decorator will attempt to decorate the argument, and then that decoratedcallablewill get used as a decorator. This is most likely not what was intended:>>> @modulate(str.lower) ... def say_excuse_me(name): ... return f"Excuse me, {name}" Traceback (most recent call last): ... TypeError: descriptor 'lower' for 'str' objects doesn't apply to a 'function' object