使用装饰器的Python日志记录

26 投票
4 回答
11023 浏览
提问于 2025-04-15 15:31

这是我们第一次接触装饰器的例子。但我不太明白我到底想要什么。

这是一个简单的装饰器,叫做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 个回答

0

我发现你的这个不错的解决方案可以稍微改进一下,如果考虑到一个通用的函数理论上可以返回一个可迭代的对象,这种情况下会抛出错误。

这里有一个解决方案:

print 'return: %s' % ret 放到一个 if 语句里:
if hasattr(ret, "__iter__"): print '返回的是可迭代对象' else: print 'return: %s' % ret

这样你就不会浪费很多时间去打印大型的可迭代对象,不过这当然可以根据个人需要进行修改。(另外,字符串没有 __iter__ 属性,这一点很方便)

1

你的函数没问题。你似乎对位置参数和可变参数(关键字参数)搞混了。

让我来解释一下:在你的例子中,ab 是位置参数,它们是必须提供的(可以有默认值)。其他的参数是可选的。如果你想让某个参数是必须的或者有默认值,就把它放在 *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})

你的函数用来分析参数是没问题的,不过我不太明白你为什么要把 varargskeywords 放到 _ 里。这样做是透明地传递参数:

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'
5

我注意到你用的这个 dict((varList[i], v) for i, v in enumerate(argt)) 其实可以用 dict(zip(varList,argt)) 来简化一下,效果是一样的。

除此之外,我还有一点建议:上面提到的内容其实不应该放在日志文件里。

与其翻阅日志去

  • 检查函数是否用正确的参数被调用,不如用断言和调试工具来做。
  • 检查函数是否返回正确的结果,应该写单元测试。

撰写回答