编辑 :我找到了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,在describe类的每个方法中添加了print语句,并更改了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()
中那样绑定了?
答案 0 :(得分:0)
我认为你试图以错误的方式解决错误的问题。但我可以解释为什么你的代码没有做你想做的事情。
首先,在你的装饰者中,你这样做:
for name, fn in inspect.getmembers(cls, inspect.ismethod):
if name != "__init__":
print "calling setlogger on %s" % cls.__name__ + "." + name
fn = setlogger(fn)
这没有效果。对于每个绑定方法fn
,您创建一个包装函数,然后将局部变量fn
重新绑定到该函数。这没有比这样做更有效:
def foo(a):
a = 3
i = 0
foo(i)
如果要在类上设置属性,则必须在类上设置属性,如下所示:
setattr(cls, name, setlogger(fn))
现在你的包装将被调用。
接下来,cls.log
是一个名为log
的类属性 - 即类本身的属性,由该类的所有实例共享。但是类中的所有代码都使用 instance 属性,其中每个实例都有自己的副本。这就是您在self.log
中分配__init__
时获得的结果。因此,没有名为log
的类属性,这意味着您只需要这样:
AttributeError: type object 'ClassA' has no attribute 'log'
你当然可以创建一个类属性......但这不会有任何好处。同名的实例属性只会遮蔽它。
您需要访问inner
中的实例属性,这意味着您需要self
才能访问它。你显然在self
内没有setlogger
。但想想你正在做什么:你用另一种方法包装方法。方法将self
作为第一个参数。事实上,如果您修改inner
以打印其args
,您会看到第一个总是类似<__main__.ClassA object at 0x12345678>
。所以:
def inner(self, *args, **kwargs):
prevname = self.log.name
self.log.name = cls.__name__
ret = func(self, *args, **kwargs) # don't forget to forward self
self.log.name = prevname
return ret
但是,如果这些包装方法中的任何一个引发异常,它们将使名称处于错误状态。实际上,您需要创建用于存储和恢复值的上下文管理器,或者只需try
/ finally
。这也恰好使包装器更容易编写:
def inner(self, *args, **kwargs):
prevname = self.log.name
self.log.name = cls.__name__
try:
return func(self, *args, **kwargs)
finally:
self.log.name = prevname
最后,您需要删除每个self.log.name =
方法中的__init__
。否则,当您在B
的中间构造A.fn1
实例时,您将更改记录器的名称,而无需通过恢复以前名称的包装器。
同样,我认为这不是一个好的解决方案。但它会做你想做的事。
答案 1 :(得分:0)
我仍然不完全理解你要解决的问题,但我认为就是这样:
构造MyLogger
需要两条信息:名称和路径。您不希望每个班级都必须知道该路径。所以,你认为你需要共享MyLogger
实例,因为没有别的办法。然后,因为MyLogger
将其名称存储为属性,所以必须在每个方法的包装器中修改该属性。
但是有一个更简单的方法:让你的类成为一个“记录器工厂” - 也就是说,一个可调用的构造适合它们的记录器 - 而不是记录器。 MyLogger
类本身已经 这样一个可调用的,因为它采用path
的默认值而你只是使用它。但是让我们假装这不是真的,你想要使用一些非默认的path
。还是很容易;你只需要把它包起来:
class ClassA(object):
def __init__(self, log_factory):
self.log_factory = log_factory
self.log = log_factory("ClassA")
def fn1(self):
# ...
b = ClassB(self.log_factory)
# ...
class ClassB(object):
def __init__(self, log_factory):
self.log_factory = log_factory
self.log = log_factory("ClassB")
# ...
# or just log_factory = functools.partial(MyLogger, log="output.log")
def log_factory(name):
return MyLogger(name, "output.log")
a = ClassA(log_factory)
a.fn1()
您可能会注意到两个类中的__init__
方法都做同样的事情。那么,为什么不将它提取到mixin基类中呢?
class LogUserMixin(object):
def __init__(self, log_factory):
self.log_factory = log_factory
self.log = log_factory(self.__class__.__name__)
现在:
class ClassA(LogUserMixin):
def fn1(self):
# ...
当ClassA
被初始化时,self.__class__
将是"ClassA"
,而不是"LogUserMixin"
,所以这完全符合您的要求。即使你的真实类已经有了基类,或者子类的层次结构,或者它们在__init__
中做了其他的东西,或者采取了其他的参数,它也能工作。你只需要在其中一些案例中做更多的工作。