class-based class decorator

Albert-Jan Roskam fomcl at yahoo.com
Mon Jan 12 08:44:36 EST 2015



----- Original Message -----

> From: Ian Kelly <ian.g.kelly at gmail.com>
> To: Python <python-list at python.org>
> Cc: 
> Sent: Saturday, January 10, 2015 7:30 AM
> Subject: Re: class-based class decorator
> 
> On Fri, Jan 9, 2015 at 2:26 PM, Albert-Jan Roskam
> 
> <fomcl at yahoo.com.dmarc.invalid> wrote:
>> 
>> 
>>  Hi,
>> 
>>  I am trying to write a class decorator that checks whether deprecated 
> parameters with non-default
>> 
>>  arguments are used. More complete code is here: 

> http://pastebin.com/ZqnMis6M.

Oops, the period was included in the url. It was 
http://pastebin.com/ZqnMis6M

>> In the code below,>>  how should I modify __call__ such that f.bar(old="oh no") prints 
> "hello world"?
>>  I thought it would be a fun way to play with the inspect module, but my 
> head is numb now and I am thirsty for a beer!
> 
> You can use inspect.getargspec to look up the default argument values
> for the target function. You can use inspect.getcallargs to map your
> *args and **kwargs to a dictionary of argument names and values. Since
> the target function will be the unbound __init__ method, you'll also
> want to pass in a dummy value like None for the self argument. Then
> you just look up each of the deprecated names in the result and flag
> any where the value doesn't match the default.
> 
> If you were using Python 3.3+, then I would recommend using
> inspect.signature instead, which is a lot more powerful. But your code
> appears to be Python 2, so we work with what we've got.


Hi Ian, Chris,

Thanks for your replies. I changed it into a regular decorator (not a class decorator). It would have been even nicer if I only needed to specify it once per class, but, well, in my case this hardly matters. The code below works as intended. One problem (not specific to the code): the decorator destroys the signature: it always becomes "*args, **kwargs"). This is annoying with help(), but what worries me most is that my Sphinx documentation is also affected. The information about the defaults gets lost (the parameters are decribed in the docstring, but not the defaults). That kind of sucks. Is there a builtin way around this (in other words: I am aware of this package: https://pypi.python.org/pypi/decorator). I am hoping to get the code working on Python 2.7 and 3.3 and up.

import functools
import inspect
import warnings

warnings.simplefilter("always")

class check_deprecated_args(object):

    def __init__(self, deprecated_params, msg=None):
        self.deprecated_params = deprecated_params
        self.msg = msg

    def __call__(self, func):
        @functools.wraps(func)
        def inner(*args, **kwargs):
            argspec = inspect.getargspec(func)
            default_signature = dict(zip(argspec.args[1:], argspec.defaults))
            callargs = inspect.getcallargs(func, *args, **kwargs)
            deprecated_calls = [(p, a) for p, a in callargs.items() if 
                                 p in self.deprecated_params and 
                                 a != default_signature[p]]
            for (param, arg) in deprecated_calls:
                msg = "you're using obsolete parameters in %s: [%s:%s]" 
                msg = msg % (func.__name__, param, arg)
                msg = msg + " " + self.msg if self.msg else msg 
                warnings.warn(msg, DeprecationWarning, stacklevel=2)
            return func(*args, **kwargs)
        functools.update_wrapper(inner, func)
        return inner

if __name__ == "__main__":
    class Foo(object):

        @check_deprecated_args(["old", "older"], "use 'brand_new' param instead")
        def __init__(self, old="old", older="ancient"):
            print "hello"

        @check_deprecated_args(deprecated_params=["old", "older"])
        def bar(self, old="default"):
            print "world"    

    f = Foo(old="old", older="dino era")
    f.bar("gnarly")

    help(f)  # now the signature is *args, **kwargs, which makes my Sphinx documentation less readable!

Best wishes,
Albert-Jan



More information about the Python-list mailing list