如何在Python中捕获调用对象的任何方法?

20

我希望您能提供一种Pythonic解决方案,用于将调用对象上的方法直接存储在对象内部。

因为在Python中,如果我想捕获例如abs()方法,我将像这样重载此运算符:

Catcher(object):
    def __abs__(self):
        self.function = abs

c = Catcher()
abs(c)  # Now c.function stores 'abs' as it was called on c

如果我想捕获一个带有另一个属性的函数,例如pow(),我将使用以下代码:

```python import functools
def my_decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): # your code here result = func(*args, **kwargs) # your code here return result return wrapper @my_decorator def pow(x, y): return x ** y ```
Catcher(object):
    def __pow__(self, value):
        self.function = pow
        self.value = value

c = Catcher()
c ** 2  # Now c.function stores 'pow', and c.value stores '2'

现在,我需要一般解决方案,以捕获和存储在 Catcher 上调用的任何类型的函数,而无需实现所有重载和其他情况。并且,正如您所看到的,我还想存储方法属性的值(如果有多个值,则可能使用列表?)。

提前感谢!


一种方法是在每个方法上都有一个装饰器来注册调用。类装饰器将允许您自动在每个方法上应用装饰器。您也可以使用元类来实现,但我想这可能会更加复杂。 - Martin Maillard
你不能覆盖 __getattr__ 方法并检查所访问的属性是否为方法吗? - Benjamin Hodgson
2
@poorsod:不,我尝试过那种方法。Dunder钩子是在上查找的,而不是实例,因此需要元类。但在那种情况下似乎没有使用__getattr____getattribute__也不行。 - Martijn Pieters
答案可能在这里:https://dev59.com/NWw05IYBdhLWcg3w6F8w - Martin Maillard
相关链接:http://code.activestate.com/recipes/366254-generic-proxy-object-with-beforeafter-method-hooks/ - Piotr Dobrogost
显示剩余4条评论
2个回答

10

元类在这里无济于事;虽然特殊方法是在当前对象的类型上查找的(因此是实例的类),但在执行此操作时不会使用__getattribute__ __getattr__(可能是因为它们本身就是特殊方法)。 因此,要捕获所有魔术方法,您需要强制创建它们。

您可以通过枚举operator模块来获得所有运算符特殊方法的相当不错的列表(如__pow____gt__等):

import operator
operator_hooks = [name for name in dir(operator) if name.startswith('__') and name.endswith('__')]

有了那个列表,一个类装饰器可以是:

def instrument_operator_hooks(cls):
    def add_hook(name):
        operator_func = getattr(operator, name.strip('_'), None)
        existing = getattr(cls, name, None)

        def op_hook(self, *args, **kw):
            print "Hooking into {}".format(name)
            self._function = operator_func
            self._params = (args, kw)
            if existing is not None:
                return existing(self, *args, **kw)
            raise AttributeError(name)

        try:
            setattr(cls, name, op_hook)
        except (AttributeError, TypeError):
            pass  # skip __name__ and __doc__ and the like

    for hook_name in operator_hooks:
        add_hook(hook_name)
    return cls

然后将其应用于您的类:

@instrument_operator_hooks
class CatchAll(object):
    pass

演示:

>>> c = CatchAll()
>>> c ** 2
Hooking into __pow__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 11, in op_hook
AttributeError: __pow__
>>> c._function
<built-in function pow>
>>> c._params
((2,), {})

因此,即使我们的类没有显式定义__pow__,我们仍然能够使用它。


由于我对@decorators还比较陌生,所以我不得不阅读这篇文章,它非常简单明了,然后我就理解了你的做法。我必须承认,现在我知道发生了什么——这不再是一种魔法:):)我在一个装饰器类中重新实现了你的解决方案——我想,在我的代码中更容易跟踪发生了什么。 - Peter Varo
@PeterVaro:没关系的。 :-) 我回答的重点是如何生成双下划线方法名称列表。 :-P - Martijn Pieters

4

这是一种实现方法。

import inspect
from functools import wraps
from collections import namedtuple

call = namedtuple('Call', ['fname', 'args', 'kwargs'])
calls = []

def register_calls(f):
    @wraps(f)
    def f_call(*args, **kw):
        calls.append(call(f.__name__, args, kw))
        print calls
        return f(*args, **kw)
    return f_call


def decorate_methods(decorator):
    def class_decorator(cls):
        for name, m in inspect.getmembers(cls, inspect.ismethod):
            setattr(cls, name, decorator(m))
        return cls
    return class_decorator


@decorate_methods(register_calls)
class Test(object):

    def test1(self):
        print 'test1'

    def test2(self):
        print 'test2'

现在所有对test1test2的调用都会被记录在calls列表中。 decorate_methods对类的每个方法应用一个装饰器。 register_callscalls中注册对方法的调用,包括函数名称和参数。

但这仍然需要您首先在类上创建所有特殊方法。 - Martijn Pieters
@morphyn 是的,Martijn Pieters是正确的,我刚刚测试过了 - 也许我没有正确使用它 - 但我无法用它做我想要的事情... - Peter Varo
是的,你仍然需要创建这些方法。我不明白你想要什么。你是在寻找 Ruby 的 method_missing 吗?那么你将需要使用 __getattr__ - Martin Maillard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接