Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #83610

Re: class-based class decorator

Date 2015-01-12 13:44 +0000
From Albert-Jan Roskam <fomcl@yahoo.com>
References <CALwzidkvGKEJtZ2f2LyfA4atY0kpaQsGrCtBro5X2TyrBe98Hw@mail.gmail.com>
Subject Re: class-based class decorator
Newsgroups comp.lang.python
Message-ID <mailman.17623.1421070586.18130.python-list@python.org> (permalink)

Show all headers | View raw



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

> From: Ian Kelly <ian.g.kelly@gmail.com>
> To: Python <python-list@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@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

Back to comp.lang.python | Previous | Next | Find similar | Unroll thread


Thread

Re: class-based class decorator Albert-Jan Roskam <fomcl@yahoo.com> - 2015-01-12 13:44 +0000

csiph-web