【问题标题】:Python class method run when another method is invokedPython 类方法在调用另一个方法时运行
【发布时间】:2016-06-15 10:03:34
【问题描述】:

我有一堂课如下:

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

是否有一个在调用方法时调用的内置函数,而不是不断运行addMethodCall()

【问题讨论】:

  • 方法也是属性,__getattr__只有在属性缺失时才会被访问。
  • @MartijnPieters 我会完善这个问题并找到一个更好的例子。
  • @MartijnPieters 问题已完善。
  • 您可以连接到__getattribute__,但我认为您要跟踪的方法上的装饰器@count_calls 会是一个更简洁的解决方案。
  • __getattribute__ 用于 all 属性访问。您可能需要清理您的术语; 类方法 是一个@classmethod-decorated 描述符。您是在说常规方法吗?

标签: python python-2.7 class


【解决方案1】:

不,没有钩子可以做到这一点。方法也是的属性,虽然有些特殊,因为它们是在访问实例上的函数对象时产生的;函数是descriptors

对方法对象的调用是一个独立于生成方法对象的步骤:

>>> 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'

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

但是,使用每次调用时递增计数器的装饰器来装饰每个方法会更容易。考虑到装饰器应用于函数绑定之前,所以你必须传递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

您仍然需要将此应用于您班级中的所有函数。您可以手动将其应用于您要计算的所有方法:

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

或者你可以使用元类:

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

这会处理装饰器所需的一切,为您设置 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

后一种方法的演示:

>>> 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

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

【讨论】:

  • 我不同意没有钩子的说法,完全可以这样做
  • @Mixone:类上没有直接挂钩。您需要了解方法是如何创建的,以及调用这些对象是如何工作的。
  • @Mixone:然后将其发布为答案。但仅仅测试某事物是可调用并不意味着它实际上会调用。
  • @Mixone:所以写下你的解决方案并发布你自己的答案。
  • @Mixone:感谢您至少将其发布为答案;很抱歉你觉得你必须删除它。该方法确实存在巨大缺陷,您计算的是属性访问(在可调用对象上过滤),而不是调用。在我的回答中,我确实说明了为什么这种区别很重要。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-28
  • 1970-01-01
  • 2014-11-07
相关资源
最近更新 更多