如何计算方法调用,而不是属性访问?

时间:2019-01-21 09:09:00

标签: python inheritance metaprogramming

我正在为多个类准备一个父类,从它的角度来看,我需要知道是否调用了特定的实例方法。

我开始从事类似的工作:

from collections import defaultdict
class ParentClass:
    def __init__(self, ):
        self.call_count = defaultdict(int)

    def __getattribute__(self, item):
        if item != 'call_count':
            self.call_count[item] += 1
        return object.__getattribute__(self, item)


class ChildClass(ParentClass):
    def child_method(self):
        pass

不幸的是,call_count还包括对该字段的访问,而无需调用它:

ob = ChildClass()

ob.child_method()
ob.child_method

assert ob.call_count['child_method'] == 1  # it's 2

如何从对象实例检测到它的字段正在被调用(不仅被访问)?

2 个答案:

答案 0 :(得分:2)

使用自定义元类的(python3)解决方案:

from collections import defaultdict
from functools import wraps
import inspect

def count_calls(func):
    name = func.__name__

    @wraps(func)
    def wrapper(self, *args, **kwargs):
        # creates the instance counter if necessary
        counter = getattr(self, "_calls_counter", None)
        if counter is None:
            counter = self._calls_counter = defaultdict(int)
        counter[name] += 1
        return func(self, *args, **kwargs)

    wrapper._is_count_call_wrapper = True
    return wrapper


class CallCounterType(type):
    def __new__(cls, name, bases, attrs):
        for name, attr in attrs.items():
            if not inspect.isfunction(attr):
                # this will weed out any callable that is not truly a function
                # (including nested classes, classmethods and staticmethods)
                continue

            try:
                argspec = inspect.getargspec(attr)
            except TypeError:
                # "unsupported callable" - can't think of any callable
                # that might have made it's way until this point and not
                # be supported by getargspec but well...
                continue

            if not argspec.args:
                # no argument so it can't be an instancemethod
                # (to be exact: a function designed to be used as instancemethod)
                # Here again I wonder which function could be found here that
                # doesn't take at least `self` but anyway...
                continue

            if getattr(attr, "_is_count_call_wrapper", False):
                # not sure why we would have an already wrapped func here but etc...
                continue

            # ok, it's a proper function, it takes at least one positional arg,
            # and it's not already been wrapped, we should be safe
            attrs[name] = count_calls(attr)

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


class ParentClass(metaclass=CallCounterType):
    pass

class ChildClass(ParentClass):
    def child_method(self):
        pass

请注意,在实例上存储调用计数只允许对实例方法调用进行计数,显然...

答案 1 :(得分:1)

以下内容有点“脏”,但是用计数功能包装所有方法可以满足您的需要:

from collections import defaultdict
class ParentClass:
    def __init__(self):
        self.call_count = defaultdict(int)

        for attr in dir(self):
            if not attr.startswith('__') and attr != '_wrapper_factory':
                callback = getattr(self, attr)
                if hasattr(callback, '__call__'):
                    setattr(self, attr, self._wrapper_factory(callback))

    def _wrapper_factory(self, callback):
        def wrapper(*args, **kwargs):
            self.call_count[callback.__name__] += 1
            callback(*args, **kwargs)
        return wrapper

class ChildClass(ParentClass):
    def child_method(self):
        pass


ob = ChildClass()

ob.child_method()
ob.child_method

assert ob.call_count['child_method'] == 1

不应给出任何断言错误。