【发布时间】:2014-02-08 09:25:29
【问题描述】:
编辑:我找到了this method decorator,并且能够使用它来单独包装ClassA 和ClassB 的方法(省略__init__)。但是,我不想手动包装单个方法,而是只包装类。
我创建了自己的日志记录类 MyLogger,它继承了 logging.Logger。在这个类中,(除其他外)我有一个 FileHandler,它在其输出中打印记录器名称:
import logging
class MyLogger(logging.Logger):
def __init__(self, name, path="output.log"):
logging.Logger.__init__(self, name, logging.DEBUG)
logpath = path
fh = logging.FileHandler(logpath)
fh.setLevel(logging.DEBUG)
fh.setFormatter(logging.Formatter("%(name)s - %(message)s"))
# stream handler omitted
self.addHandler(fh)
我也有 ClassA 和 ClassB,它们都获得相同的 MyLogger 实例:
class ClassA(object):
def __init__(self, mylogger):
self.log = mylogger
def fn1(self):
self.log.debug("message1 from ClassA fn1")
self.fn2()
b = ClassB(self.log)
b.fn1()
self.log.debug("message2 from ClassA fn1")
def fn2(self):
self.log.debug("message1 from ClassA fn2")
# many more functions
class ClassB(object):
def __init__(self, mylogger):
self.log = mylogger
def fn1(self):
self.log.debug("message1 from ClassB fn1")
# many more functions
这是一个简单的“主”函数:
print "inside main"
log = MyLogger("main")
a = ClassA(log)
a.fn1()
因为正在传递 MyLogger 实例,所以我想确保每个函数正确打印日志名称(我只是使用类名)。所以我试图装饰每个类的所有方法,以便记住以前的日志名称,然后将日志名称设置为类的名称,运行方法,最后将日志名称设置回什么以前是。我正在使用来自here 的装饰器/描述符。为简洁起见,我只会发布我的更改。我重命名了装饰器 setlogger,在 descript 类的每个方法中添加了打印语句,并将make_bound 更改如下:
def make_bound(self, instance):
print "in __BOUND__"
@functools.wraps(self.f)
def wrapper(*args, **kwargs):
'''This documentation will disapear :)'''
prev = instance.log.name
print "about to wrap %s.%s, prev = %s" % (instance.__class__.__name__, self.f.__name__, prev)
ret = self.f(instance, *args, **kwargs)
instance.log.name = prev
print "done wrapping %s.%s, now = %s" % (instance.__class__.__name__, self.f.__name__, prev)
return ret
# This instance does not need the descriptor anymore,
# let it find the wrapper directly next time:
setattr(instance, self.f.__name__, wrapper)
return wrapper
如果我使用 setlogger 装饰器/描述符将单个方法包装在 ClassA 和 ClassB 中,它可以正常工作。但是,我想只包装这两个类。所以这是我的班级装饰器:
def setloggerforallmethods(cls):
def decorate(*args, **kwargs):
for name, m in inspect.getmembers(cls, inspect.ismethod):
if name != "__init__":
print "calling setattr on %s.%s" % (cls.__name__, name)
setattr(cls, name, setlogger(m))
return cls
return decorate
如果我用 @setloggerforallmethods 包装 ClassA 和 ClassB,并运行 main 函数,输出如下:
inside main
calling setattr on ClassA.fn1
in __INIT__: f = fn1
calling setattr on ClassA.fn2
in __INIT__: f = fn2
in __GET__
in __UNBOUND__
Traceback (most recent call last):
File "/ws/maleva-rcd/yacht/classa.py", line 23, in <module>
a.fn1()
File "/ws/maleva-rcd/yacht/yachtlogger.py", line 34, in wrapper
self.f.__name__)
ValueError: zero length field name in format
我不明白为什么此时 fn1 未绑定。不是像a.fn1()那样绑定到a吗?
【问题讨论】:
-
首先,这显然是 Python 2.x,因此您应该始终使用
class A(object):创建新样式的类。您不希望在尝试调试的任何内容之上添加经典类的怪癖。 -
第二,你不应该明确地调用
logging.Logger.__init__。如果你想构造一个 Logger,你可以像任何其他类型一样做:调用类作为构造函数。 (如果您的意思是让MyLogger成为logging.Logger的子类,那就不同了……但是您实际上必须将其设为子类。)这意味着您的代码甚至无法运行并且表现出你所询问的行为,它只是得到一个TypeError试图传递一个非Logger作为self参数logging.Logger.__init__。 -
最后,你的
ClassB中没有任何东西可以初始化一个新的记录器,或者重新初始化一个现有的记录器,所以你问的问题不可能存在于你的代码中。 -
另外,
self.log不是类属性,它是实例属性。这意味着每个ClassA对象(以及每个ClassB对象,不用说)都有一个不同的对象。而只是做fn = setlogger(fn)并没有做任何有用的事情,它只是创建了一个包装函数,将它绑定到本地名称fn,然后忘记它。如果你想对一个对象的属性进行monkeypatch,你必须在对象上实际调用setattr。 -
@abarnert:如果我理解他的例子,他会预先创建一个记录器并将其作为参数传递给构造函数,所以同一个对象将被共享为
self.log。