我们可以编写某种日志记录装饰器来回显函数/方法调用,如下所示:
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会怎样?有没有办法将一个装饰器放在类定义之上,以使其所有方法调用都被装饰/记录?还是有一些其他更好,更有趣的方法来做而不是装饰?
答案 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