Python: Why can't I get my decorator to work?
This works now for those new to this question:
class ensureparams(object):
"""
Used as a decorator with an iterable passed in, this will look for each item
in the iterable given as a key in the params argument of the function being
decorated. It was built for a series of PayPal methods that require
different params, and AOP was the best way to handle it while staying DRY.
>>> @ensureparams(['name', 'pass', 'code'])
... def complex_function(params):
... print(params['name'])
... print(params['pass'])
... print(params['code'])
>>>
>>> params = {
... 'name': 'John Doe',
... 'pass': 'OpenSesame',
... #'code': '1134',
... }
>>>
>>> complex_function(params=params)
Traceback (most recent call last):
...
ValueError: Missing from "params" dictionary in "complex_function": code
"""
def __init__(self, required):
self.required = set(required)
def __call__(self, func):
def wrapper(*args, **kwargs):
if not kwargs.get('params', None):
raise KeyError('"params" kwarg required for {0}'.format(func.__name__))
missing = self.required.difference(kwargs['params'])
if missing:
raise ValueError('Missing from "params" dictionary in "{0}": {1开发者_如何学Python}'.format(func.__name__, ', '.join(sorted(missing))))
return func(*args, **kwargs)
return wrapper
if __name__ == "__main__":
import doctest
doctest.testmod()
def wrapper(params):
means you're only going to accept one argument -- and so of course calls with (self, params)
just won't work. You need to be able to accept either one or two arguments, e.g., at the very least (if you don't need to support calls w/named args):
def wrapper(one, two=None):
if two is None: params = one
else: params = two
# and the rest as above
You can get much more complex / sophisticated in order to also accept named arguments, but this is much simpler and still "mostly works";-).
Decorators normally look like this:
def wrapper(*args, **kargs):
# Pull what you need out of the argument lists and do stuff with it
func(*args, **kargs)
Then they work with any function passed to them, not just functions with a specific number of arguments or with specific keyword arguments. In this specific case, you may want to do some introspection on the func passed to __call__
to find out if it's a one or two argument function and to make sure the last argument is called 'params'. Then just write wrapper
like this:
def wrapper(*args):
params = args[-1]
missing = self.required.difference(params)
if missing:
raise ValueError('Missing from "params" argument: %s' % ', '.join(sorted(missing)))
func(params)
What I did was add *args, **kwargs, and just check for the keys that are required within the 'params' argument via kwargs['params'] after checking that kwargs params exists.
Here's the new version (which works perfectly):
class requiresparams(object):
"""
Used as a decorator with an iterable passed in, this will look for each item
in the iterable given as a key in the params argument of the function being
decorated. It was built for a series of PayPal methods that require
different params, and AOP was the best way to handle it while staying DRY.
>>> @requiresparams(['name', 'pass', 'code'])
... def complex_function(params):
... print(params['name'])
... print(params['pass'])
... print(params['code'])
>>>
>>> params = {
... 'name': 'John Doe',
... 'pass': 'OpenSesame',
... #'code': '1134',
... }
>>>
>>> complex_function(params=params)
Traceback (most recent call last):
...
ValueError: Missing from "params" dictionary: code
"""
def __init__(self, required):
self.required = set(required)
def __call__(self, func):
def wrapper(*args, **kwargs):
if not kwargs.get('params', None):
raise KeyError('"params" kwarg required for {0}'.format(func.__name__))
missing = self.required.difference(kwargs['params'])
if missing:
raise ValueError('Missing from "params" dictionary: %s' % ', '.join(sorted(missing)))
return func(*args, **kwargs)
return wrapper
if __name__ == "__main__":
import doctest
doctest.testmod()
精彩评论