使用装饰器进行 Python 日志记录

发布于 2024-08-09 15:05:22 字数 1143 浏览 3 评论 0原文

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

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

我觉得这并不像我想象的那么容易,但奇怪的是我没有在谷歌上找到我想要的东西。

你能告诉我我的解决方案是否可以吗?或者你能为我提出的问题提出更好的解决方案吗?

谢谢大家。

This is the first example we meet when we face with decorators. But I'm not able to realize what exactly I would like.

A simple decorator named LOG. It should work like this:

@LOG
def f(a, b=2, *c, **d):
    pass

And the result should be something like:

f(1, pippo=4, paperino='luca')
===== Enter f =====
a = 1
b = 2
pippo = 4
paperino = luca
===== Exit f =====

Where every argument passed as a parameter to the function is showed with its value.

I discovered that the problem is harder than I thought, mainly because of the many different ways you can pass arguments to a function (think about tuples with *c or dictionaries with **d).

I tried a solution but I'm not sure it's correct. It' somethink like this:

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

I think it's not so easy as I expected, but it's strange I didn't found what I wanted on Google.

Can you tell me if my solution is ok? Or can you suggest a better solution to the problem I proposed?

Thank you to everybody.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

懒猫 2024-08-16 15:05:22

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

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

您无需通过日志查看

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

The only thing I noticed is that the dict((varList[i], v) for i, v in enumerate(argt)) construct you used twice is actually dict(zip(varList,argt)).

Other than that i only have meta-criticism: None of the above belong in a logfile.

Instead of going trough the logs to

  • see if functions are called with the correct args you use asserts and a debugger.
  • see if function return the correct results you write unittests.
断爱 2024-08-16 15:05:22

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

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

Everyhing is ok in your function. You seem to be lost with positional vs variable&keyword arguments.

Let me explain: positional arguments, a and b in your case, are obligatory (and may have default values). Other arguments are optional. If you want to make an argument obligatory or to have a default value, put it before *args and **kwargs. But remember that you can't supply an argument twice:

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'

There's another way, but not that readable, to have default values for arguments and have no positional args:

def x(*args, **kwargs):
    kwargs.updae({'a': 1, 'b': 2})

Your function that analyses the arguments is ok, though I don't understand why you write varargs and keywords into _. It passes arguments transparently:

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'
So要识趣 2024-08-16 15:05:22

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

这是一个解决方案:

print 'return: %s' % ret 包装到 if 语句中:
if hasattr(ret, "__iter__"):
打印“返回可迭代”
别的:
print '返回:%s' % ret

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

I found your nice solution can slightly be improved upon, if you take into account that a general function theoretically can return an iterable, in which case an error is thrown.

Here is a solution for this:

Wrap print 'return: %s' % ret into an if statement:
if hasattr(ret, "__iter__"):
print 'returned iterable'
else:
print 'return: %s' % ret

This way you don't either use a lot of time printing large iterables, but that can of course be modified according to ones needs. (Also a string doesn't have an __iter__ attribute, which is handy)

葮薆情 2024-08-16 15:05:22

Polog 库提供了针对此问题的现成解决方案。

安装它:

$ pip install polog

并使用:

from polog import log, config, file_writer

config.add_handlers(file_writer())

@log("my message")
def f(a, b=2, *c, **d):
    pass

f(1, pippo=4, paperino='luca')

控制台中的结果:

[2022-10-30 21:42:32.704898] |    1    | SUCCESS |  AUTO  | "my message" | where: __main__.f() | time of work: 0.00001597 sec. | input variables: 1 (int), pippo = 4 (int), paperino = "luca" (str) | result: None (NoneType)

A ready-made solution to this problem is offered in the Polog library.

Install it:

$ pip install polog

And use:

from polog import log, config, file_writer

config.add_handlers(file_writer())

@log("my message")
def f(a, b=2, *c, **d):
    pass

f(1, pippo=4, paperino='luca')

The result in a console:

[2022-10-30 21:42:32.704898] |    1    | SUCCESS |  AUTO  | "my message" | where: __main__.f() | time of work: 0.00001597 sec. | input variables: 1 (int), pippo = 4 (int), paperino = "luca" (str) | result: None (NoneType)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文