Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing the class that owns a decorated method from the decorator

I'm writing a decorator for methods that must inspect the parent methods (the methods of the same name in the parents of the class in which I'm decorating).

Example (from the fourth example of PEP 318):

def returns(rtype):
    def check_returns(f):
        def new_f(*args, **kwds):
            result = f(*args, **kwds)
            assert isinstance(result, rtype), \
                   "return value %r does not match %s" % (result,rtype)
            return result
        new_f.func_name = f.func_name
        # here I want to reach the class owning the decorated method f,
        # it should give me the class A
        return new_f
    return check_returns

class A(object):
    @returns(int)
    def compute(self, value):
        return value * 3

So I'm looking for the code to type in place of # here I want...

Thanks.

like image 222
Gra Avatar asked Dec 18 '22 08:12

Gra


2 Answers

As bobince said it, you can't access the surrounding class, because at the time the decorator is invoked, the class does not exist yet. If you need access to the full dictionary of the class and the bases, you should consider a metaclass:

__metaclass__

This variable can be any callable accepting arguments for name, bases, and dict. Upon class creation, the callable is used instead of the built-in type().

Basically, we convert the returns decorator into something that just tells the metaclass to do some magic on class construction:

class CheckedReturnType(object):
    def __init__(self, meth, rtype):
        self.meth = meth
        self.rtype = rtype

def returns(rtype):
    def _inner(f):
        return CheckedReturnType(f, rtype)
    return _inner

class BaseInspector(type):
    def __new__(mcs, name, bases, dct):
        for obj_name, obj in dct.iteritems():
            if isinstance(obj, CheckedReturnType):
                # do your wrapping & checking here, base classes are in bases
                # reassign to dct
        return type.__new__(mcs, name, bases, dct)

class A(object):
    __metaclass__ = BaseInspector
    @returns(int)
    def compute(self, value):
        return value * 3

Mind that I have not tested this code, please leave comments if I should update this.

There are some articles on metaclasses by the highly recommendable David Mertz, which you might find interesting in this context.

like image 142
Torsten Marek Avatar answered Feb 08 '23 22:02

Torsten Marek


here I want to reach the class owning the decorated method f

You can't because at the point of decoration, no class owns the method f.

class A(object):
    @returns(int)
    def compute(self, value):
        return value * 3

Is the same as saying:

class A(object):
    pass

@returns(int)
def compute(self, value):
    return value*3

A.compute= compute

Clearly, the returns() decorator is built before the function is assigned to an owner class.

Now when you write a function to a class (either inline, or explicitly like this) it becomes an unbound method object. Now it has a reference to its owner class, which you can get by saying:

>>> A.compute.im_class
<class '__main__.A'>

So you can read f.im_class inside ‘new_f’, which is executed after the assignment, but not in the decorator itself.

(And even then it's a bit ugly relying on a CPython implementation detail if you don't need to. I'm not quite sure what you're trying to do, but things involving “get the owner class” are often doable using metaclasses.)

like image 23
bobince Avatar answered Feb 08 '23 23:02

bobince