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

Dra*_*uan 18 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):
        ...
Run Code Online (Sandbox Code Playgroud)

但是如果我们想要记录方法调用而不在每个方法定义的前面放置那么多的@log会怎样?有没有办法将一个装饰器放在类定义之上,以使其所有方法调用都被装饰/记录?还是有其他更好,更有趣的方法来做而不是装饰?

Ned*_*der 16

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

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)
Run Code Online (Sandbox Code Playgroud)


Mah*_*der 11

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

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

#!/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)
Run Code Online (Sandbox Code Playgroud)

输出:

called  foo
called  bar
called  foobar
Run Code Online (Sandbox Code Playgroud)

  • @sblom,你为什么编辑这个?如果使用普通的查找方法存在属性,那么`__getattr__`将*不会被调用,这将使用`__getattribute__`.这就是为什么我重写了`__getattribute__` NOT`__getattr__`.你可以在编辑代码之前至少运行代码:) (4认同)

nco*_*lan 8

请参阅将装饰器附加到类中的所有函数

但是,正如该问题的公认答案所指出的那样,这通常不是一个好主意.

如果您决定采用面向方面的编程路线,我建议从这里开始:任何针对Python的AOP支持库?


Ale*_*lex 8

它可以通过许多不同的方式完成。我将展示如何通过元类类装饰器继承来实现

通过改变元类

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
Run Code Online (Sandbox Code Playgroud)

此外,我将展示两种方法如何在不改变类元信息的情况下(通过类装饰器类继承)。第一种方法通过类装饰器 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
Run Code Online (Sandbox Code Playgroud)

而且,最近,我遇到了同样的问题,但我无法将装饰器放在类上或以任何其他方式更改它,除非我被允许仅通过继承添加此类行为(我不确定这是如果您可以根据需要更改代码库,则是最佳选择)。

这里 classLogger强制子类的所有可调用成员编写有关其调用的信息,请参阅下面的代码。

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
Run Code Online (Sandbox Code Playgroud)

或者更抽象地说,您可以基于一些装饰器实例化基类。

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
Run Code Online (Sandbox Code Playgroud)