Лучший способ записывать вызовы методов в Python?

Мы можем закодировать некоторый вид декоратора логирования для эхо-вызовов функций / методов, как показано ниже:

def log(fn):
    ...

@log
def foo():
    ...

class Foo(object):
    @log
    def foo(self):
        ...

    @log
    def bar(self, a, b):
        ...

    @log
    def foobar(self, x, y, z):
        ...

Но что, если мы хотим регистрировать вызовы методов, не помещая столько @log перед каждым определением мета? Есть ли способ поместить один декоратор выше определения класса, чтобы все вызовы его методов были оформлены / записаны? Или есть другие, более интересные и интересные способы сделать это вместо декоратора?

4 ответа

Решение

См. Присоединение декоратора ко всем функциям в классе

Однако, как указывает принятый ответ на этот вопрос, это, как правило, не очень хорошая идея.

Если вы решите пойти по пути аспектно-ориентированного программирования, я предлагаю начать здесь: Любая библиотека поддержки AOP для Python?

Это может быть излишним, но есть функция трассировки, которая проинформирует вас о большой активности в вашей программе:

import sys

def trace(frame, event, arg):
    if event == "call":
        filename = frame.f_code.co_filename
        if filename == "path/to/myfile.py":
            lineno = frame.f_lineno
            # Here I'm printing the file and line number, 
            # but you can examine the frame, locals, etc too.
            print "%s @ %s" % (filename, lineno)
    return trace

sys.settrace(trace)
call_my_function()
sys.settrace(None)

Это можно сделать разными способами. Я покажу, как это сделать через мета-класс, декоратор классов и наследование.

путем изменения мета - класс

import functools


class Logger(type):
    @staticmethod
    def _decorator(fun):
        @functools.wraps(fun)
        def wrapper(*args, **kwargs):
            print(fun.__name__, args, kwargs)
            return fun(*args, **kwargs)
        return wrapper

    def __new__(mcs, name, bases, attrs):
        for key in attrs.keys():
            if callable(attrs[key]):
                # if attrs[key] is callable, then we can easily wrap it with decorator
                # and substitute in the future attrs
                # only for extra clarity (though it is wider type than function)
                fun = attrs[key]
                attrs[key] = Logger._decorator(fun)
        # and then invoke __new__ in type metaclass
        return super().__new__(mcs, name, bases, attrs)


class A(metaclass=Logger):
    def __init__(self):
        self.some_val = "some_val"

    def method_first(self, a, b):
        print(a, self.some_val)

    def another_method(self, c):
        print(c)

    @staticmethod
    def static_method(d):
        print(d)


b = A()
# __init__ (<__main__.A object at 0x7f852a52a2b0>,) {}

b.method_first(5, b="Here should be 5")
# method_first (<__main__.A object at 0x7f852a52a2b0>, 5) {'b': 'Here should be 5'}
# 5 some_val
b.method_first(6, b="Here should be 6")
# method_first (<__main__.A object at 0x7f852a52a2b0>, 6) {'b': 'Here should be 6'}
# 6 some_val
b.another_method(7)
# another_method (<__main__.A object at 0x7f852a52a2b0>, 7) {}
# 7
b.static_method(7)
# 7

Кроме того, покажет два подход, как сделать это без изменения метаинформации класса (через класс декоратор и наследование класса). Первый подход через декоратор классов put_decorator_on_all_methods принимает декоратор для обертывания всех вызываемых объектов класса.

def logger(f):
    @functools.wraps(f)
    def wrapper(*args, **kwargs):
        print(f.__name__, args, kwargs)
        return f(*args, **kwargs)

    return wrapper


def put_decorator_on_all_methods(decorator, cls=None):
    if cls is None:
        return lambda cls: put_decorator_on_all_methods(decorator, cls)

    class Decoratable(cls):
        def __init__(self, *args, **kargs):
            super().__init__(*args, **kargs)

        def __getattribute__(self, item):
            value = object.__getattribute__(self, item)
            if callable(value):
                return decorator(value)
            return value

    return Decoratable


@put_decorator_on_all_methods(logger)
class A:
    def method(self, a, b):
        print(a)

    def another_method(self, c):
        print(c)

    @staticmethod
    def static_method(d):
        print(d)


b = A()
b.method(5, b="Here should be 5")
# >>> method (5,) {'b': 'Here should be 5'}
# >>> 5
b.method(6, b="Here should be 6")
# >>> method (6,) {'b': 'Here should be 6'}
# >>> 6
b.another_method(7)
# >>> another_method (7,) {}
# >>> 7
b.static_method(8)
# >>> static_method (8,) {}
# >>> 8

И недавно я столкнулся с той же проблемой, но я не мог поместить декоратор в класс или изменить его каким-либо другим способом, за исключением того, что мне было разрешено добавлять такое поведение только через наследование (я не уверен, что это лучший выбор, если вы можете изменить кодовую базу по своему желанию).

Вот класс Logger заставляет все вызываемые члены подклассов записывать информацию об их вызовах, см. код ниже.

class Logger:

    def _decorator(self, f):
        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            print(f.__name__, args, kwargs)
            return f(*args, **kwargs)

        return wrapper

    def __getattribute__(self, item):
        value = object.__getattribute__(self, item)
        if callable(value):
            decorator = object.__getattribute__(self, '_decorator')
            return decorator(value)
        return value


class A(Logger):
    def method(self, a, b):
        print(a)

    def another_method(self, c):
        print(c)

    @staticmethod
    def static_method(d):
        print(d)

b = A()
b.method(5, b="Here should be 5")
# >>> method (5,) {'b': 'Here should be 5'}
# >>> 5
b.method(6, b="Here should be 6")
# >>> method (6,) {'b': 'Here should be 6'}
# >>> 6
b.another_method(7)
# >>> another_method (7,) {}
# >>> 7
b.static_method(7)
# >>> static_method (7,) {}
# >>> 7

Или, более абстрактно, вы можете создать экземпляр базового класса на основе некоторого декоратора.

def decorator(f):
    @functools.wraps(f)
    def wrapper(*args, **kwargs):
        print(f.__name__, args, kwargs)
        return f(*args, **kwargs)
    return wrapper


class Decoratable:
    def __init__(self, dec):
        self._decorator = dec

    def __getattribute__(self, item):
        value = object.__getattribute__(self, item)
        if callable(value):
            decorator = object.__getattribute__(self, '_decorator')
            return decorator(value)
        return value


class A(Decoratable):
    def __init__(self, dec):
        super().__init__(dec)

    def method(self, a, b):
        print(a)

    def another_method(self, c):
        print(c)

    @staticmethod
    def static_method(d):
        print(d)

b = A(decorator)
b.method(5, b="Here should be 5")
# >>> method (5,) {'b': 'Here should be 5'}
# >>> 5
b.method(6, b="Here should be 6")
# >>> method (6,) {'b': 'Here should be 6'}
# >>> 6
b.another_method(7)
# >>> another_method (7,) {}
# >>> 7
b.static_method(7)
# >>> static_method (7,) {}
# >>> 7

Я не уверен, какой у вас вариант использования для этого, но в целом я бы больше подумал о том, что именно является проблемой, которую вы пытаетесь решить.

Тем не менее, вот пример, который может делать то, что вы хотите, но без декоратора:

#!/usr/bin/env python
import inspect


class Foo(object):

    def foo(self):
        pass

    def bar(self, a, b):
        pass

    def foobar(self, x, y, z):
        pass

    def __getattribute__(self, name):
        returned = object.__getattribute__(self, name)
        if inspect.isfunction(returned) or inspect.ismethod(returned):
            print 'called ', returned.__name__
        return returned


if __name__ == '__main__':
    a = Foo()
    a.foo()
    a.bar(1, 2)
    a.foobar(1, 2, 3)

Выход:

called  foo
called  bar
called  foobar

Что ж, если вы не хотите явно декорировать все свои функции, вы можете получить все функции / методы данного модуля и автоматически применить ваш декоратор. не самая простая вещь, но не невозможная в питоне:)

Вы также можете попробовать аспектно-ориентированную среду программирования.

my2c

Другие вопросы по тегам