开发者

Python __metaclass__ inheritance issue

My issue is that I am using a metaclass to wrap certain class methods in a timer for logging purposes.

For example:

class MyMeta(type):

    @staticmethod
    def time_method(method):
        def __wrapper(self, *args, **kwargs):
            start = time.time()
            result = method(self, *args, **kwargs)
            finish = time.time()
            sys.stdout.write('instancemethod %s took %0.3f s.\n' %(
                method.__name__, (finish - start)))
            return result
        return __wrapper

    def __new__(cls, name, bases, attrs):
        for attr in ['__init__', 'run']:
            if not attr in attrs:
                continue

            attrs[attr] = cls.time_method(attrs[attr])

        return super(MetaBuilderModule, cls).__new__(cls, name, bases, attrs)

The problem I'm having is that my wrapper runs for every '__init__' even though I really only want it for the current module I am instantiating. The same goes for any method want to time. I dont want the timing to run on any inherited methods UNLESS they aren't being overridden.

class MyC开发者_JAVA百科lass0(object):
    __metaclass__ = MyMeta
    def __init__(self):
        pass

    def run(self): 
        sys.stdout.write('running')
        return True


class MyClass1(MyClass0):
    def __init__(self): # I want this timed
        MyClass0.__init__(self) # But not this.
        pass

    ''' I need the inherited 'run' to be timed. '''

I've tried a few things but so far I've had no success.


Guard the timing code with an attribute. That way, only the outermost decorated method on an object will actually get timed.

@staticmethod
def time_method(method):
    def __wrapper(self, *args, **kwargs):
        if hasattr(self, '_being_timed'):
            # We're being timed already; just run the method
            return method(self, *args, **kwargs)
        else:
            # Not timed yet; run the timing code
            self._being_timed = True  # remember we're being timed
            try:
                start = time.time()
                result = method(self, *args, **kwargs)
                finish = time.time()
                sys.stdout.write('instancemethod %s took %0.3f s.\n' %(
                    method.__name__, (finish - start)))
                return result
            finally:
                # Done timing, reset to original state
                del self._being_timed
    return __wrapper

Timing only the outermost method is slightly different than “not timing inherited methods unless they aren't being overridden”, but I believe it solves your problem.


I'm not sure this has anything to do with multiple inheritance.

The trouble is that any subclass of MyClass0 has to be an instance of the same metaclass, which means MyClass1 gets created with MyMeta.__new__, so its methods get processed and wrapped in the timing code.

Effectively, what you need is that MyClass0.__init__ somehow returns something different in the two following circumstances:

  1. When called directly (instantiating MyClass0 directly, or when MyClass1 doesn't override it), it needs to return the timed method
  2. When called within a subclass definition, it needs to return the original untimed method

This is impossible, since MyClass0.__init__ doesn't know why it's being called.

I see three options:

  1. Make the metaclass more complex. It can check through the base classes to see if they're already instances of the metaclass; if so it can make a new copy of them that removes the timed wrapper from the methods that are present in the class being constructed. You don't want to mutate the base classes directly, as that will affect all uses of them (including when they're instantiated directly, or when they're subclassed by other classes that override different methods). A downside of this is it really screws up the instanceof relationships; unless you construct the slight variations on the base classes by creating new subclasses of them (ugh!) and caching all the variations so you never construct duplicates (ugh!), you completely void natural assumptions that two classes share a base class (they may only share a template from which two completely independent base classes were generated).
  2. Make the timing code more complex. Have a start_timing and stop_timing method, and if start_timing is called when the method is already being timed you just increment a counter, and stop_timing just decrements a counter and only stops timing when the counter hits zero. Be careful of timed methods that call other timed methods; you'll need to have separate counters per method name.
  3. Give up on metaclasses and just use a decorator on the methods you want timed explicitly, with some way of getting at the undecorated method so that overriding definitions can call it. This will involve a couple of lines of boiler plate per use; that will quite possibly add up to less lines of code than either of the other two options.
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜