Python 对类和任何子类的某些方法的调用计数

Python 对类和任何子类的某些方法的调用计数,python,metaclass,Python,Metaclass,我有一个泛型类,用户应该将其子类化以实现某些方法。可以有几个层次的子类。差不多 class Thing(object): def fun(self, *args, **kwargs): raise NotImplementedError() class Bell(Thing): def fun(self): return 1 class Whistle(Bell): def fun(self): return super

我有一个泛型类,用户应该将其子类化以实现某些方法。可以有几个层次的子类。差不多

class Thing(object):
    def fun(self, *args, **kwargs):
        raise NotImplementedError()

class Bell(Thing):
    def fun(self):
        return 1

class Whistle(Bell):
    def fun(self):
        return super(Whistle, self).fun() + 1
我想计算使用
Thing
的任何子类时调用
fun()
的次数。因为decorator不是继承的,而且因为我不希望用户必须记住装饰他们的
fun()
方法,所以我的理解是元类是一种方法。所以我写了

class CountCalls(type):

    def __new__(cls, name, bases, attrs):
        attrs["_original_fun"] = attrs["fun"]
        attrs["fun"] = countcalls(attrs["_original_fun"])
        return super(CountCalls, cls).__new__(cls, name, bases, attrs)
其中,
countcalls
是计算调用数的经典装饰器:

def countcalls(fn):
    def wrapper(*args, **kwargs):
        wrapper.ncalls += 1
        return fn(*args, **kwargs)
    wrapper.ncalls = 0
    wrapper.__name__ = fn.__name__
    wrapper.__doc__ = fn.__doc__
    return wrapper
并将
事物的定义更改为

class Thing(object):

    __metaclass__ = CountCalls

    def fun(self, *args, **kwargs):
        raise NotImplementedError()
问题:这是可行的,但它会产生意外的副作用,即在调用任何实例的
fun()
方法时,增加所有实例的调用次数计数器:

>>> b1 = Bell()
>>> b2 = Bell()
>>> b1.fun.ncalls, b2.fun.ncalls
(0, 0)
>>> b1.fun()
1
>>> b1.fun.ncalls, b2.fun.ncalls
(1, 1)
问题:如何计算每个实例调用
fun()
的次数?感觉我应该在元类中实现
\uuuu init\uuuuu
而不是
\uuu new\uuuuu
,但到目前为止,我还没有找到正确的语法。例如,使用

def __init__(self, name, bases, attrs):
    attrs["_original_fun"] = attrs["fun"]
    attrs["fun"] = countcalls(attrs["_original_fun"])
    super(CountCalls, self).__init__(name, bases, attrs)
屈服

>>> b = Bell()
>>> b.fun.ncalls
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'function' object has no attribute 'ncalls'
>b=Bell()
>>>b.fun.ncalls
回溯(最近一次呼叫最后一次):
文件“”,第1行,在
AttributeError:“函数”对象没有属性“nCall”

谢谢

您可以通过稍微更改继承模式跳过元类:

class Thing(object):
    def __init__(self):
        self.fun_calls = 0

    def fun(self, *args, **kwargs):
        self.fun_calls += 1
        self._fun(*args, **kwargs)

    def _fun(self, *args, **kwargs):
        raise NotImplementedError()

然后在子类中重写
\u fun
。这使您可以自动计算每个实例的数量,而且它(imo)比元类实现更干净、更容易理解

为了跟踪每个实例而不是每个函数的调用,您需要在实例上使用变量来跟踪它,类似于:

def countcalls(fn):
    def wrapper(self,*args, **kwargs):
        self._calls_dict[wrapper]+=1
        return fn(self,*args, **kwargs)
    wrapper.__name__ = fn.__name__
    wrapper.__doc__ = fn.__doc__
    return wrapper
尽管您需要某种方式为第一次调用初始化字典:

import collections

def countcalls(fn):
    def wrapper(self,*args, **kwargs):
        if not hasattr(self,"_calls_dict"):
            self._calls_dict = collections.defaultdict(int)
        self._calls_dict[wrapper.__name__]+=1
        return fn(self,*args, **kwargs)
    wrapper.__name__ = fn.__name__
    wrapper.__doc__ = fn.__doc__
    return wrapper
尽管请注意,如果将此修饰符应用于
classmethod
s或
staticmethod
s,则此修饰符将崩溃,因此请注意如何实现此修饰符

这也使如何检索调用数变得复杂,但使用单独的函数可以相当容易地完成:

from types import MethodType

def get_calls(method):
    if not isinstance(method,MethodType):
        raise TypeError("must pass bound method as argument")

    func = method.__func__
    inst = method.__self__

    dct = getattr(inst,"_calls_dict",None)
    if dct is None or func not in dct:
        return 0 #maybe raise an error instead?
    else:
        return dct[func]

优雅的,简单的,蟒蛇式的。在我看来,这是最好的方法。我同意这是最简单的方法,尽管它没有提供一种方法来自动计算对每个已定义方法的调用数,这可能就是OP首先使用元类的原因。谢谢。我欣赏这种方法的简单性。我以前也做过类似的事情,但后来放弃了,因为实现一个名称不能直接反映您正在实现的功能的方法对用户不友好。