Python中更好的记录方法调用的方式是什么?

29

我们可以编写一些日志装饰器来输出函数/方法调用,例如下面的代码:

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,有没有一种方法可以只在类定义上方放置一个装饰器,以使其所有方法调用都被装饰/记录?或者是否有其他更好和有趣的方式来实现这个目的,而不是使用装饰器?


难道明确记录事情不更有意义吗?我理解记录函数调用可以帮助更好地了解何时发生了什么,但通常来说,至少我更喜欢在适当的时候进行一些记录。 - poke
5个回答

32

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

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)

正是我所需要的。使用frame.f_code.co_name获取方法名称。相关文档在此处:https://docs.python.org/3/library/inspect.html#module-inspect - Samuel

15

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

通过更改元类实现

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

14

我不确定您使用这个的情况是什么,但通常我会更多地考虑您正在尝试解决的问题是什么。

话虽如此,这里有一个示例,可能可以实现您想要的功能,但不需要装饰器:

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

9

请注意,第一个链接问题的答案中方法的“事后”装饰可以轻松地移动到类装饰器中,而不是在类定义之后内联执行。 - ncoghlan

0

如果您不想显式地装饰所有函数,可以获取给定模块的所有函数/方法并自动应用装饰器。这不是最简单的事情,但在Python中并非不可能 :)

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

我的两分意见


2
对于sys.modules.items()中的name和module,以及module.dict.items()中的fname和obj: 如果obj具有'call'属性,则将log(obj)赋值给sys.modules['name'].dict[fname]。 - aoeu256

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接