我正在为多个类准备一个父类,从它的角度来看,我需要知道是否调用了特定的实例方法。
我开始从事类似的工作:
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
如何从对象实例检测到它的字段正在被调用(不仅被访问)?
答案 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
不应给出任何断言错误。