开发者

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()
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜