【问题标题】:Python logging using a decorator使用装饰器进行 Python 日志记录
【发布时间】:2010-12-11 13:06:54
【问题描述】:

这是我们面对装饰器时遇到的第一个例子。但我无法意识到我到底想要什么。

一个名为 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

我认为这并不像我想象的那么容易,但奇怪的是我没有在 Google 上找到我想要的东西。

您能告诉我我的解决方案是否可行吗?或者您能针对我提出的问题提出更好的解决方案吗?

谢谢大家。

【问题讨论】:

  • 有效吗?如果它有效,那么没关系。如果没有,那么你就有问题了。向我们询问有关该问题的具体问题。
  • 是的,你是对的。实际上它似乎有效。但是正如我所写的,有许多复杂的方法可以将参数传递给 Python 函数:使用元组、字典、默认参数……实际上,即使它看起来有效,我不确定它是否是正确的实现,它可以在每个案例。
  • 我自己也成为了它的牺牲品,这个函数,在你所描述的绝对一般的形式中,对于你继续开发你的应用程序是必要的吗?听起来日志记录机制本身已经吸引了你的注意力,而且我从经验中知道这可能是一个可怕的生产力杀手。
  • 我完全同意你的看法。继续我的工作对我来说并不重要。由于它不是那么重要并且我无法解决问题,因此我将其移至待办事项列表的底部。但现在这是一种练习......我想知道是否有一个通用、优雅和简单的解决方案来解决我所描述的问题。
  • 别担心,我的大脑也是这样工作的。而且我怀疑这个网站上还有很多其他人......

标签: python logging introspection decorator


【解决方案1】:

我唯一注意到的是,您使用两次的dict((varList[i], v) for i, v in enumerate(argt)) 构造实际上是dict(zip(varList,argt))

除此之外,我只有元批评:以上都不属于日志文件。

而不是通过日志去

  • 查看函数是否使用正确的参数调用,您使用断言和调试器。
  • 查看函数是否返回您编写的单元测试的正确结果。

【讨论】:

  • 您能否详细说明/提供有关 (1) 的链接?
  • 感谢您提供有用的 zip 功能。我认为你是正确的元批评。我在远程机器上开发,我不能使用调试器。所以这种日志记录机制应该是一种简单的调试方式。
  • 批评元批评:如果生产中存在错误,您将很乐意采取任何可以得到的日志。因此,所有这些信息都属于日志。
【解决方案2】:

您的功能一切正常。您似乎迷失了位置与变量和关键字参数。

让我解释一下:在您的情况下,位置参数 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'

【讨论】:

    【解决方案3】:

    我发现你的好解决方案可以稍微改进,如果你考虑到一般函数理论上可以返回一个可迭代的,在这种情况下会引发错误。

    这是一个解决方案:

    print 'return: %s' % ret 包装成一个if 语句:
    if hasattr(ret, "__iter__"): print 'returned iterable' else: print 'return: %s' % ret

    这样您既不会花费大量时间打印大型迭代,但当然可以根据需要进行修改。 (而且字符串没有__iter__ 属性,这很方便)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-06-05
      • 2014-11-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-16
      • 2018-11-19
      • 1970-01-01
      相关资源
      最近更新 更多