调用另一个方法时运行Python类方法

tec*_*ner 3 python class python-2.7

我有一个课程如下:

class MyClass(object):
    def __init__(self):
        self.foo = "foo"
        self.bar = "bar"
        self.methodCalls = 0  #tracks number of times any function in the instance is run

    def get_foo(self):
        addMethodCall()
        return self.foo

    def get_bar(self):
        addMethodCall()
        return self.bar

    def addMethodCall(self):
        self.methodCalls += 1
Run Code Online (Sandbox Code Playgroud)

是否有一个内置函数,只要调用方法而不是持续运行,就会调用它addMethodCall()

Mar*_*ers 7

不,类上没有钩子可以做到这一点.方法是属性,尽管在他们正在访问实例上的函数对象时产生有些特别; 函数是描述符.

然后,对方法对象的调用与生成方法对象的步骤不同:

>>> class Foo(object):
...     def bar(self):
...         return 'bar method on Foo'
...
>>> f = Foo()
>>> f.bar
<bound method Foo.bar of <__main__.Foo object at 0x100777bd0>>
>>> f.bar is f.bar
False
>>> stored = f.bar
>>> stored()
'bar method on Foo'
Run Code Online (Sandbox Code Playgroud)

object.__getattribute__()方法的任务是调用描述符协议,因此您可以挂钩以查看何时生成方法,但您仍然需要生成的方法对象包装起来以检测调用.例如,您可以使用代理实际方法的__call__方法返回对象.

但是,使用装饰器装饰每个方法会更容易,装饰器每次调用时都会递增计数器.考虑装饰器绑定之前应用于函数,因此您必须传递self:

from functools import wraps

def method_counter(func):
    @wraps(func)
    def wrapper(self, *args, **kwargs):
        self.methodCalls += 1
        return func(self, *args, **kwargs)
    return wrapper
Run Code Online (Sandbox Code Playgroud)

您仍然需要将其应用于班级中的所有功能.您可以手动将其应用于您想要计算的所有方法:

class MyClass(object):
    def __init__(self):
        self.foo = "foo"
        self.bar = "bar"
        self.methodCalls = 0  #tracks number of times any function method is run

    @method_counter
    def get_foo(self):
        return self.foo

    @method_counter
    def get_bar(self):
        return self.bar
Run Code Online (Sandbox Code Playgroud)

或者您可以使用元类:

import types

class MethodCounterMeta(type):
    def __new__(mcls, name, bases, body):
        # create new class object
        for name, obj in body.items():
            if name[:2] == name[-2:] == '__':
                # skip special method names like __init__
                continue
            if isinstance(obj, types.FunctionType):
                # decorate all functions
                body[name] = method_counter(obj)
        return super(MethodCounterMeta, mcls).__new__(mcls, name, bases, body)

    def __call__(cls, *args, **kwargs):
        # create a new instance for this class
        # add in `methodCalls` attribute
        instance = super(MethodCounterMeta, cls).__call__(*args, **kwargs)
        instance.methodCalls = 0
        return instance
Run Code Online (Sandbox Code Playgroud)

这会照顾装饰者需要的一切,methodCalls为你设置一个属性,所以你的类不必:

class MyClass(object):
    __metaclass__ = MethodCounterMeta
    def __init__(self):
        self.foo = "foo"
        self.bar = "bar"

    def get_foo(self):
        return self.foo

    def get_bar(self):
        return self.bar
Run Code Online (Sandbox Code Playgroud)

演示后一种方法:

>>> class MyClass(object):
...     __metaclass__ = MethodCounterMeta
...     def __init__(self):
...         self.foo = "foo"
...         self.bar = "bar"
...     def get_foo(self):
...         return self.foo
...     def get_bar(self):
...         return self.bar
...
>>> instance = MyClass()
>>> instance.get_foo()
'foo'
>>> instance.get_bar()
'bar'
>>> instance.methodCalls
2
Run Code Online (Sandbox Code Playgroud)

上面的元类只考虑函数对象(所以def语句和lambda表达式的结果)是类体的一部分用于装饰.它忽略任何其他可调用对象(有更多类型具有__call__方法,例如functools.partial对象),以及稍后添加到类中的函数.

  • @Mixone:那么*张贴作为一个答案*.但只是测试某些东西是*可调用的*并不意味着它实际上会被*调用. (2认同)
  • @Mixone:所以写下你的解决方案并发布你自己的答案. (2认同)
  • @Mixone:至少感谢你发贴这个答案; 对不起,你觉得你必须删除它.这种方法确实有很大的缺陷,你在计算属性访问权限(在可调用对象上过滤),而不是调用.在这里我的答案我做节目为什么区别非常重要. (2认同)