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

Posted

技术标签:

【中文标题】如何在python中捕获对对象调用的任何方法?【英文标题】:How to catch any method called on an object in python? 【发布时间】:2013-04-28 15:26:16 【问题描述】:

我正在寻找关于如何存储在对象内部的对象上调用的方法的 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(),我将使用它:

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 上调用的任何类型的函数。如您所见,我还想存储方法的属性值(可能在一个列表中,如果有多个?)。

提前致谢!

【问题讨论】:

那些不委托给 dunder 方法的函数呢? 你可能想看看类装饰器和元类。 @delnan 我猜,这些也可以,因为在我的例子中,这些函数正在寻找其他东西,一个值或一个方法来调用。 @morphyn 你能更具体一点吗? AFAIK 元类非常适合创建类对象,不是吗? @poorsod:不,我尝试过这种方法。 Dunder 钩子在 class 上查找,而不是在实例上查找,因此需要一个元类。但是__getattr__ 在这种情况下似乎没有使用。 __getattribute__ 也没有。 【参考方案1】:

元类在这里无济于事;尽管在当前对象的类型上查找特殊方法(因此实例的类),但这样做时不会咨询__getattribute____getattr__(可能是因为它们本身就是特殊方法)。因此,要捕获 all dunder 方法,您必须全部创建它们。

您可以通过枚举operator module 来获得所有运算符 特殊方法(__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 很陌生,所以我不得不阅读这个article,这非常简单,然后我才明白,你做了什么......我不得不承认,现在,我知道发生了什么——它不再是那种魔法了 :):) 我在装饰器类中重新实现了你的解决方案——我想,更容易理解我的代码中发生的事情。跨度> @PeterVaro:没关系。 :-) 我回答的重点是如何生成一个 dunder-method 名称列表。 :-P【参考方案2】:

这是一种方法。

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 list 中注册。

decorate_methods 将装饰器应用于类的每个方法。 register_calls 使用函数名和参数注册对calls 中方法的调用。

【讨论】:

但这仍然需要你先在类上创建all特殊方法。 @morphyn 是的,Martijn Pieters 是对的,我刚刚测试过这个——也许我没有正确使用它——但我不能用这个做我想做的事...... 是的,您仍然需要创建方法。我不明白你想要什么。您正在寻找 ruby​​ 的 method_missing 然后 :) 您将不得不使用 __getattr__

以上是关于如何在python中捕获对对象调用的任何方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何查找对类的所有对象的所有调用

python类中如何自动调用函数?

如何从 Python Tkinter 应用程序捕获到控制台的任何输出?

如何在 Python 2.x 中对对象执行自省?

如何在调用 python 脚本时捕获 python 脚本的结果? [复制]

如何捕获ctypes中抛出的异常?