Descriptors and decorators

Discussion in 'Python' started by Joost Molenaar, Oct 25, 2010.

  1. WebOb contains this little nugget of code that allows you to define a
    decorator that works on both methods and functions:

    class Decorator(object):
    def __init__(self, func):
    self.func = func
    def __get__(self, object, type=None):
    if type is None:
    return self
    newfunc = self.func.__get__(object, type)
    return self.__class__(newfunc)

    I adapted it into a class, so that I can inherit this functionality
    without thinking about it:

    class trace(Decorator):
    def __call__(self, *a, **kw):
    print '-->', self.func.__name__, repr(a), repr(kw)
    result = self.func(*a, **kw)
    print '<--', self.func.__name__, '=', repr(result)
    return result

    I can then use it like this:

    class C(object):
    @trace
    def m(self, x):
    return 2 * x

    And like this:

    @trace
    def f(x):
    return 2 * x

    It works:

    >>> o = C()
    >>> o.m(21)

    --> m (21,) {}
    <-- m = 42
    >>> f(21)

    --> f (21,) {}
    <-- f = 42

    I'm still not sure what Decorator.__get__ does, or at least I'm not
    sure enough to be able to explain it well.

    Logically, the method C.m is unbound at the time the class is defined
    but o.m is bound when it is called. This means that what
    Decorator.__init__ receives as its 'func' parameter is the unbound
    method C.m, and when it runs it should operate on the bound method o.m
    instead. I suspect that this is what happens inside Decorator.__get__:
    create a new instance of the decorator, with a bound version of the
    decorated method, and call that without needing a 'self' parameter.

    Is this in fact the easiest way to explain it?

    Joost Molenaar
    Joost Molenaar, Oct 25, 2010
    #1
    1. Advertising

  2. Joost Molenaar

    Guest

    On 25 oct, 14:15, Joost Molenaar <> wrote:
    > WebOb contains this little nugget of code that allows you to define a
    > decorator that works on both methods and functions:
    >
    > class Decorator(object):
    >     def __init__(self, func):
    >         self.func = func
    >     def __get__(self, object, type=None):
    >         if type is None:
    >             return self
    >         newfunc = self.func.__get__(object, type)
    >         return self.__class__(newfunc)

    (snip)

    > I'm still not sure what Decorator.__get__ does, or at least I'm not
    > sure enough to be able to explain it well.


    http://wiki.python.org/moin/FromFunctionToMethod


    > Logically, the method C.m is unbound at the time the class is defined


    "At the time the class is defined" - that is (usually) when the
    "class" statement's body is executed -, 'm' is a plain function. Note
    that by that time, class 'C' doesn't even exist, so there's no way you
    could have a 'C.m' method ;)

    So, your decorator is applied to a function, and wraps it into a
    Decorator object. Or more exactly, the function is defined, then the
    Decorator class is called so a new Decorator object is instanciated
    with the function as argument, and finally this Decorator instance is
    rebound to the name under which the function was formely known. All
    this happenning _before_ class C even exists, so when the class object
    is created, it has an attribute by the name of the decorated function
    which is in fact a Decorator instance.

    Now this instance is itself a descriptor, so when C.m or o.m are
    looked up, the descriptor protocol is fired and Descriptor.__get__ is
    called. If called without at least a 'type' argument, it just returns
    itself, so it works as an ordinary function. Else it calls the
    function's own descriptor implementation to get a bound or unbound
    method, and returns a new instance of the decorator with the method as
    argument.

    HTH
    , Oct 25, 2010
    #2
    1. Advertising

  3. On 25 October 2010 15:20,
    <> wrote:
    > So, your decorator is applied to a function, and wraps it into a
    > Decorator object. Or more exactly, the function is defined, then the
    > Decorator class is called so a new Decorator object is instanciated
    > with the function as argument, and finally this Decorator instance is
    > rebound to the name under which the function was formely known. All
    > this happenning _before_ class C even exists, so when the class object
    > is created, it has an attribute by the name of the decorated function
    > which is in fact a Decorator instance.
    >
    > Now this instance is itself a descriptor, so when C.m or o.m are
    > looked up, the descriptor protocol is fired and Descriptor.__get__ is
    > called. If called without at least a 'type' argument, it just returns
    > itself, so it works as an ordinary function. Else it calls the
    > function's own descriptor implementation to get a bound or unbound
    > method, and returns a new instance of the decorator with the method as
    > argument.


    Thanks, Bruno.
    Your python-wiki page and walk-through for the Decorator code make it
    clear. I now finally understand that methods are in fact ordinary
    functions at the time the class is created, and that the descriptor
    protocol turns them into bound or unbound methods when they're
    accessed as attributes:

    >>> class K(object): pass

    ....
    >>> def g(self): pass

    ....
    >>> g

    <function g at 0x7f322f701838>
    >>> K.m = g
    >>> K.m

    <unbound method K.g>
    >>> K.__dict__['m']

    <function g at 0x7f322f701838>
    >>> K().m

    <bound method K.g of <__main__.K object at 0x7f322f709a50>>

    Cheers! Now I will try to wrap my brain around metaclasses and coroutines. ;-)
    Joost Molenaar, Oct 25, 2010
    #3
  4. Joost Molenaar

    Guest

    On 25 oct, 17:18, Joost Molenaar <> wrote:
    > Thanks, Bruno.
    > Your python-wiki page and walk-through for the Decorator code make it
    > clear. I now finally understand that methods are in fact ordinary
    > functions at the time the class is created, and that the descriptor
    > protocol turns them into bound or unbound methods when they're
    > accessed as attributes:

    (snip)
    > Cheers! Now I will try to wrap my brain around metaclasses and coroutines. ;-)


    Metaclasses are nothing special, really. Python classes are plain
    objects and you can as well instanciate a class directly - the "class"
    statement being mostly syntactic sugar:

    def func(obj, x):
    obj.x = x

    NewClass = type("NewClass", (object,), {'__init__':func, 'foo':lambda
    z: z.x + 2})

    So in the end, a metaclass is just another plain class, that is used
    to instanciate class objects.
    , Oct 26, 2010
    #4
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. rooban
    Replies:
    0
    Views:
    594
    rooban
    Jul 21, 2004
  2. Diez B. Roggisch
    Replies:
    1
    Views:
    281
    Peter Hansen
    Nov 29, 2003
  3. Adrien Di Mascio

    descriptors and old-style classes

    Adrien Di Mascio, Jan 27, 2004, in forum: Python
    Replies:
    1
    Views:
    292
    John Roth
    Jan 27, 2004
  4. Arien Malec

    PEP 318 decorators are not Decorators

    Arien Malec, Aug 13, 2004, in forum: Python
    Replies:
    11
    Views:
    556
    Arien Malec
    Aug 16, 2004
  5. Antoon Pardon
    Replies:
    14
    Views:
    446
    Steve Holden
    Mar 1, 2005
Loading...

Share This Page