Как поймать любой метод, вызываемый объектом в python? - программирование
Подтвердить что ты не робот

Как поймать любой метод, вызываемый объектом в python?

Я ищу питоновское решение о том, как хранить метод, вызываемый объектом прямо внутри объекта.

Поскольку в 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, без реализации всех перегрузок и других случаев. И как вы можете видеть, я также хочу сохранить значения (возможно, в списке, если их более одного?), Которые являются атрибутами метода.

Спасибо заранее!

4b9b3361

Ответ 1

Метаклас не поможет здесь; хотя специальные методы просматриваются по типу текущего объекта (поэтому класс для экземпляров), __getattribute__ или __getattr__ не рассматриваются при этом (вероятно, потому, что они сами являются специальными методами). Итак, чтобы поймать все методы dunder, вы вынуждены создавать их все.

Вы можете получить довольно приличный список всех специальных методов оператора (__pow__, __gt__ и т.д.), перечислив operator module:

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__, мы все еще подключаемся к нему.

Ответ 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'

Теперь все вызовы test1 и test2 будут записываться в calls list.

decorate_methods применяет декоратор к каждому методу класса. register_calls регистрирует вызовы методов в calls, с именем функции и аргументами.