Decorator which conditionally activates another decorator?
I have some functions which, under normal circumstances, are called with arguments provided by user input. It is, however, valid to call some of these functions with certain series of arguments which are determined at runtime based on some system state.
I would like for the user to be able to optionally instruct my program to call these functions with all valid input and return the results of each call. I think a decorator which would work something like an activation switch for functions which have another decorator which indicates which series of arguments to use would work well.
Additionally, I need to preserve the function signature and metadata. It's vital to the operation of my program.
This is what I've tried, but it doesn't work. It is based upon this example.
>>> from decorator import decorator >>> def the_list(): ... return ["foo", "bar", "baz"] ... >>> import itertools >>> @decorator ... def do_all(func): ... # this will do nothing (besides wrap in a tuple) unless func is decorated with @gets_arg_from ... if hasattr(func, 'get_from'): ... return tuple(func(*args) for args in itertools.product(*(list_fun() for list_fun in func.get_from))) ... else: ... return (func(),) ... >>> def gets_arg_from(*list_funcs): ... # this will do nothing to func unless decorated with @do_all ... def gets_arg_from(func, *args, **kwargs): ... func.get_from = list_funcs ... return func(*args, **kwargs) ... return decorator(gets_arg_from) ... >>> @gets_arg_from(the_list) ... def print_func(word): ... # this will print its argument unless decorated with @do_all ... # at that point it will print every element returned by the_list() ... print word ... >>> print_func("foo") foo >>> all = decorator(do_all, print_func) >>> all() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: print_func() takes exactly 1 argument (0 given) >>> print_func.get_from Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'function' object has no attribute 'get_from'
What I expected was:
>>> all() ("foo", "bar", "baz")
What I've noticed is wrong:
- gets_arg_from doesn't add the get_from attribute to func.
- Something about me using the notation @gets_arg_from(the_list) is wrong. It thinks I am trying to pass two arguments (but why would that be a problem anyway?)
As for my motivation, I think of decorators for this because there are literally hundreds of these routines, their implementation details (as well as their functional correctness) is subject to frequent change, and I don't want to use inspect to reason what to do based on their argument names nor do I want to hard-code the do_all functionality for each function for which it makes sense. Class methods might work, but for my purpose, they're semantically contrived. Furthermore, for the sake of others who may have to maintain my code, I think it is easier to ask them to apply a decorator and not to worry about the rest rather than to use certain argument names or place the function in a certain class or whatever. I realize this question may sound strange, so I figured this footnote might help make me look less like a madman.
Isn't this doing the thing you want?
import functools from itertools import product def the_list(): return ["foo", "bar", "baz"] def do_all(func): if hasattr(func, 'get_from'): @functools.wraps(func) def wrapper(*args, **kwargs): return tuple(func(*args) for args in product(*(lf() for lf in func.get_from))) return wrapper return func def gets_arg_from(*list_funcs): def decorator(func): func.get_from = list_funcs return func return decorator @gets_arg_from(the_list) def print_func(word): return word print print_func('foo') all = do_all(print_func) print all()
These two code segments are identical:
@deco def func(...): some code
is the same as
func = deco(lambda ...: some code)
@something is just a syntactic sugar for the function call and anonymous function creation...
I'll explain what happened in the next peace of code step by step:
@gets_arg_from(the_list) def print_func(word): return word
First Python creates an anonimous function that receives a parameter word and has a body that just returns this word (or does whatever the function body does)
Then the function get_arg_from gets called and the_list gets passed to it as an argument
get_arg_from creates a decorator function and returnes it
The decorator function returned from the get_arg_from is called (this is the syntactic sugar thing) passing as an argument func the anonimous function created in the step 1.
decorator just assigns the list_funcs tuple to the get_from attribute of the anonymous function and returns the anonimous function
The return value of the decorator function is assigned to a variable print_func
Similar effect can be achieved with:
def __anonimous(word): return word __decorator = gets_arg_from(the_list) print_func = __decorator(__anonimous)
So basically gets_arg_from is not a decorator it's a function that returns a decorator.
do_all on the other hand is a decorator, it receives a function as an argument, and returns either the original function (if the function doesn't have the attribute get_from) or a wrapper function which replaces the original function (if it has the get_from attribute).
You can find more examples here.