在Python中记录方法调用的更好方法?

时间:2011-02-24 11:02:59

标签: python logging

我们可以编写某种日志记录装饰器来回显函数/方法调用,如下所示:

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会怎样?有没有办法将一个装饰器放在类定义之上,以使其所有方法调用都被装饰/记录?还是有一些其他更好,更有趣的方法来做而不是装饰?

5 个答案:

答案 0 :(得分:15)

这可能有点矫枉过正,但有一个跟踪功能工具可以告诉您程序中的大量活动:

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)

答案 1 :(得分:10)

我不确定你的用例是什么,但一般来说,我会更多地考虑你要解决的问题究竟是什么。

那就是说,这里有一个例子可能会做你想要的但没有装饰者:

#!/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

答案 2 :(得分:8)

请参阅Attaching a decorator to all functions within a class

然而,正如该问题的公认答案所指出的那样,它通常不是一个好主意。

如果你决定采用面向方面的编程路线,我建议从这里开始:Any AOP support library for Python?

答案 3 :(得分:3)

我将展示两种方法,如何在不更改类的元信息的情况下实现它(通过类装饰器和类继承,尽管在文章结尾处也有一部分更改了元)。

通过类装饰器@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

当然,一切都可以通过更改类的元类来完成

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

答案 4 :(得分:0)

好吧,如果你不想显式地装饰你的所有函数,你可以获得给定模块的所有函数/方法并自动应用你的装饰器。不是最容易的事情,但在python中不可行:)

您还可以尝试面向方面的编程框架。

MY2C