Python: Very confused about decorators
I thought I understood decorators but not anymore. Do decorators only work when the function is created?
I wanted to create a series of functions that all have a required argument called 'ticket_params' that is a dictionary. and then decorate them with something like @param_checker(['req_param_1', 'req_param_2'])
and then if 'req_param_1' and 'req_param_2' aren't in the dictionary, raise a custom Exception subclass. Am I thinking of this all wrong?
It would be something like this in the开发者_Go百科 calling code:
@param_checker(['req_param_1', 'req_param_2'])
def my_decorated_function(params):
# do stuff
params = {'req_param_1': 'Some Value'}
my_decorated_function(params)
# exception would be raised here from decorator.
A decorator is applied immediately after the def
statement; the equivalence is:
@param_checker(['req_param_1', 'req_param_2'])
def my_decorated_function(params):
# do stuff
is exactly the same thing as:
def my_decorated_function(params):
# do stuff
my_decorated_function = param_checker(['req_param_1', 'req_param_2'])(my_decorated_function)
So the job of param_checker
is to return a function that takes as its argument the function to be decorated and returns yet another function which does what you require. OK so far?
Edit: so, here's one implementation...:
import functools
def param_checker(reqs):
reqs = set(reqs)
def middling(f):
@functools.wraps(f)
def wrapper(params):
missing = reqs.difference(params)
if missing:
raise TypeError('Missing parms: %s' % ', '.join(sorted(missing)))
return f(params)
return wrapper
return middling
Decorators are only called once on a function, that is, when the def
statement is parsed like so:
@mydecorator
def myfunction(): ...
I assume you mean something like that:
class param_checker:
def __init__(self, l):
self.l = l
def __call__(self, functionToBeDecorated):
def wrapper(*args, **kwargs):
if any(necessary not in kwargs["ticket_params"] for necessary in self.l):
raise MyCustomException
return functionToBeDecorated(*args, **kwargs)
return wrapper
Please tell me if you don't understand that ;)
Here's a full example based on @AndiDog's example. Remember any callable can be used as a decorator, it doesn't have to be a class.
class MyCustomException(Exception):
pass
# The decorator - instances of this class are callable as it implements __call__
class param_checker:
# In this example l is the parameter you pass to the decorator.
# For example, l could be ['req_param_1', 'req_param_2'].
def __init__(self, l):
self.l = l
# This makes the instance callable
def __call__(self, functionToBeDecorated):
def wrapper(*args, **kwargs):
# For the successful call below args = () and
# kwargs = {'ticket_params': {'req_param_1': 'param_1', 'req_param_2': 'param_2'}}
if "ticket_params" not in kwargs or any(necessary not in kwargs["ticket_params"] for necessary in self.l):
# if the ticket params parameter has not been specified, or if
# any of the required parameters are not present raise an exception
raise MyCustomException
return functionToBeDecorated(*args, **kwargs)
return wrapper
@param_checker(['req_param_1', 'req_param_2'])
def myfunction(ticket_params=None):
# if the two required params are present this will print
print "params ", ticket_params
if __name__ == "__main__":
try:
myfunction()
except MyCustomException:
print "all required params not supplied"
try:
myfunction(ticket_params={'req_param_1': 'param_1'})
except MyCustomException:
print "all required params not supplied"
myfunction(ticket_params={'req_param_1': 'param_1', 'req_param_2': 'param_2'})
Check Alex's answer in order to understand python decorators; by the way:
1) what aren't you understanding of decorators? Don't you understand decorators as a general concept, or Python decorators? Beware, the "classical" decorator pattern, java annotations and python decorators are different things.
2) python decorators should always return a function, e.g. in your code the return value of param_checker([...]) should be a function that accepts a function as param (the func to be decorated), and returns a function with the same signature as my_decorated_function. Take a look at the following example; the decorator function is executed just once (when the class is created), while the decorated func is then executed at every call. In this specific example, it then invokes the original func, but that's not a requirement.
def decorator(orig_func):
print orig_func
def decorated(self, a):
print "aahahah", orig_func(self, a)
return decorated
class Example(object):
@decorator
def do_example(self, a):
return 2 * a
m = Example()
m.do_example(1)
3) you might not be doing the best thing in the way you're using decorators. They should usually be employed when there's some concept which is quite orthogonal to what you're actually programming, and can be reused around - it's essentially the Python way of doing AOP. Your param_checker might not be that orthogonal - if your decorator gets just used once, then it's probably not a good place to use a decorator at all. Your param_checker seems to be the case - it assumes that the decorated func takes a single arg which is a dictionary - are there many funcs in your code with such a signature and behaviour? If the answer is "no", just check for params at the begininng of the func and raise an exception if they're missing.
This is a good explanation for those asking the same question:
# This does nothing.
class donothing(object):
def __init__(self, func):
"""
The 'func' argument is the function being decorated because in this
case, we're not instantiating the decorator class. Instead we are just
using the class object as a callable (a class is always callable as this
is how an instance is returned) to use as a decorator, which means that
it is being instantiated upon definition of the decorated function and
the decorated function is being passed in as an argument to the class's
__init__ method.
"""
self.func = func
def __call__(self, *args, **kwargs):
"""
The __call__ function is called when the decorated function is called
because the function has be eaten by the decorator class. Now it's up to
the this method to return a call to the original function. The arguments
are passed in as args, kwargs to be manipulated.
"""
# Returns original function call with original arguments.
return self.func(*args, **kwargs)
@donothing
def printer(text):
print(text)
printer('hello world')
# The printer function is now an alias for the donothing instance created, so
# the preceding was the same as:
#
# instance = donothing(printer)
# instance('hello world')
#
# Next example:
class checkforkeysinparams(object):
def __init__(self, required):
self.required = set(required)
def __call__(self, params):
def wrapper(params):
missing = self.required.difference(params)
if missing:
raise TypeError('Missing from "params" argument: %s' % ', '.join(sorted(missing)))
return wrapper
# Apply decorator class, passing in the __init__'s 'required' argument.
@checkforkeysinparams(['name', 'pass', 'code'])
def complex_function(params):
# Obviously these three are needed or a KeyError will be raised.
print(params['name'])
print(params['pass'])
print(params['code'])
# Create params to pass in. Note, I've commented out one of the required params.
params = {
'name': 'John Doe',
'pass': 'OpenSesame',
#'code': '1134',
}
# This call will output: TypeError: Missing from "params" argument: code
complex_function(params=params)
精彩评论