ShaharA
ShaharA

Reputation: 903

Wrap all class methods using a meta class

I'm trying to wrap all of the methods inside a class I wrote with a specific wrapper method.

My class inherits from the python dict class, and I want to wrap all of the methods of this parent class, such as __setitem__, __getitem__, etc.

In my attempts to achieve that I have written a meta class that wraps all the methods inside it's child class, using the __init__ method in the meta class, so I can access the child class's object (and not it's class definition which does not include the parent methods).

However, after running the code, I see the wrapper method is never called. Meaning the wrapping didn't succeed.

Can you help with figuring out what went wrong?

My code:

def wrapper(func):
    def wrapped(self, *args, **kwargs):
        print 'wrapper.__call__()'
        res = func(self, *args, **kwargs)
        return res
    return wrapped

class MyMeta(type):   
    def __init__(cls, classname, bases, class_dict):
        print 'meta.__init__()'
        new_class_dict = {}
        for attr_name in dir(cls):
            attr = getattr(cls, attr_name)
            if hasattr(attr, '__call__'):
                attr = wrapper(attr)
            new_class_dict[attr_name] = attr
        return super(MyMeta, cls).__init__(classname, bases, new_class_dict)

class MyDict(dict):

    __metaclass__ = MyMeta

    def __init__(self, *args):
        print 'child.__init__()'
        super(MyDict, self).__init__(*args)     

d = MyDict({'key': 'value'})
d['new_key'] = 'new_value'

The printout I get is:

meta.__init__()
child.__init__()

without any reference to the wrapper.__call__() print I placed inside the wrapped method...

Upvotes: 2

Views: 1270

Answers (1)

bruno desthuilliers
bruno desthuilliers

Reputation: 77902

When the metaclass __init__ gets called, the class object has already been built so modifying the attributes dict (class_dict in your code) at this stage is totally useless indeed. You want to use setattr instead:

class MyMeta(type):   
    def __init__(cls, classname, bases, class_dict):
        for attr_name in dir(cls):
            if attr_name == "__class__":
                # the metaclass is a callable attribute too, 
                # but we want to leave this one alone
                continue
            attr = getattr(cls, attr_name)
            if hasattr(attr, '__call__'):
                attr = wrapper(attr)
                setattr(cls, attr_name, attr)

        # not need for the `return` here
        super(MyMeta, cls).__init__(classname, bases, class_dict)

Upvotes: 3

Related Questions