Descriptors and decorators

J

Joost Molenaar

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:
--> m (21,) {}
<-- m = 42--> 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
 
B

bruno.desthuilliers

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
 
J

Joost Molenaar

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
K.m = g
K.m
K.__dict__['m']
<bound method K.g of <__main__.K object at 0x7f322f709a50>>

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

bruno.desthuilliers

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.
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,764
Messages
2,569,564
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top