使用装饰器的Python日志记录
这是我们第一次接触装饰器的例子。但我不太明白我到底想要什么。
这是一个简单的装饰器,叫做LOG。它应该像这样工作:
@LOG
def f(a, b=2, *c, **d):
pass
结果应该是这样的:
f(1, pippo=4, paperino='luca')
===== Enter f =====
a = 1
b = 2
pippo = 4
paperino = luca
===== Exit f =====
在这里,传递给函数的每个参数都会显示它的值。
我发现这个问题比我想的要复杂,主要是因为有很多不同的方式可以给函数传递参数(比如用 *c 的元组或者用 **d 的字典)。
我尝试了一个解决方案,但不确定它是否正确。大概是这样的:
def LOG(fn):
import inspect
varList, _, _, default = inspect.getargspec(fn)
d = {}
if default is not None:
d = dict((varList[-len(default):][i], v) for i, v in enumerate(default))
def f(*argt, **argd):
print ('Enter %s' % fn).center(100, '=')
d.update(dict((varList[i], v) for i, v in enumerate(argt)))
d.update(argd)
for c in d.iteritems():
print '%s = %s' % c
ret = fn(*argt, **argd)
print 'return: %s' % ret
print ('Exit %s' % fn).center(100, '=')
return ret
return f
我觉得这没有我预想的那么简单,但奇怪的是我在谷歌上没有找到我想要的答案。
你能告诉我我的解决方案是否可行吗?或者你能建议一个更好的解决方案吗?
谢谢大家。
4 个回答
我发现你的这个不错的解决方案可以稍微改进一下,如果考虑到一个通用的函数理论上可以返回一个可迭代的对象,这种情况下会抛出错误。
这里有一个解决方案:
把
print 'return: %s' % ret
放到一个 if 语句里:
if hasattr(ret, "__iter__"): print '返回的是可迭代对象' else: print 'return: %s' % ret
这样你就不会浪费很多时间去打印大型的可迭代对象,不过这当然可以根据个人需要进行修改。(另外,字符串没有 __iter__
属性,这一点很方便)
你的函数没问题。你似乎对位置参数和可变参数(关键字参数)搞混了。
让我来解释一下:在你的例子中,a
和 b
是位置参数,它们是必须提供的(可以有默认值)。其他的参数是可选的。如果你想让某个参数是必须的或者有默认值,就把它放在 *args 和 **kwargs 之前。但要记住,你不能给同一个参数提供两次:
def x(a = 1, b = 2, *args, **kwargs):
print a, b, args, kwargs
>>> x(3, 4, 5, b=6)
TypeError: x() got multiple values for keyword argument 'b'
还有一种方法可以为参数设置默认值,并且不使用位置参数,不过这种方法可读性不太好:
def x(*args, **kwargs):
kwargs.updae({'a': 1, 'b': 2})
你的函数用来分析参数是没问题的,不过我不太明白你为什么要把 varargs
和 keywords
放到 _
里。这样做是透明地传递参数:
def x(a = 1, b = 2, *args, **kwargs):
print a, b, args, kwargs
def y(*args, **kwargs):
x(*args, **kwargs)
>>> y(3, 4, 5, 6)
3 4 (5, 6) {}
>>> y(3, 4, 5, b=6)
TypeError: x() got multiple values for keyword argument 'b'
我注意到你用的这个 dict((varList[i], v) for i, v in enumerate(argt))
其实可以用 dict(zip(varList,argt))
来简化一下,效果是一样的。
除此之外,我还有一点建议:上面提到的内容其实不应该放在日志文件里。
与其翻阅日志去
- 检查函数是否用正确的参数被调用,不如用断言和调试工具来做。
- 检查函数是否返回正确的结果,应该写单元测试。