【问题标题】:How to specify float precision from variable in logging using % formatting?如何使用 % 格式从变量中指定浮点精度?
【发布时间】:2022-01-14 17:04:58
【问题描述】:

我想避免在日志中对 f-strings 进行急切的评估,因此我使用 %-formatting。

精度由变量precision定义。

import logging
value = 3.14159
logging.error("%.2f", value)

precision = 2

logging.error(f"{value:.{precision}f}") # OK but eager evaluation
logging.error("%.*f", value, precision) # fails
logging.error("%.%if", value, precision) # fails

有什么建议吗?

【问题讨论】:

  • 为什么急切评估在这里是个问题?如果您将格式字符串保存在变量中,这只会是一个问题。
  • @Barmar:我认为他们正在进行过早的优化,并试图避免在日志级别不足以发出字符串时格式化字符串的成本。对于error 级别,这可能毫无意义(您不太可能忽略错误级别消息),但对于闲聊的调试消息,当调试之外的 99% 的使用都不会使用时,避免创建完整字符串可能会有一点好处它。也就是说,我怀疑它在大多数情况下是否重要。 f-strings 变得非常 很快,而日志记录所涉及的其他开销可能超过了避免 temp str 所节省的微不足道的成本。
  • @Barmar 这里没有问题,但遵循使用惰性格式进行日志记录的做法。我使用了.error,以便可以复制粘贴我的最小代码并直接查看输出(无需额外的行来更改调试级别)。
  • @thomast:很高兴你能想到这一点。我会注意到,在实践中(在使用 logging.basicConfig(filename='/dev/null') 以确保输出没有炸毁我的终端之后,刚刚使用 IPython 微基准测试),避免急切格式化的增量好处实际上非常小(尽管显然急切的 printf风格的方法仍然胜过 f 弦)。我将logging.debug('%.*f', precision, value) 与logging.debug('%.*f' % (precision, value)) 进行了比较,lazy 的每次调用成本为 424 ns,而 Eager printf 样式为 682 ns(等效 f-string 为 849 ns)。
  • 没错,这里急切格式化的成本是 1.5-2 倍。但是基线成本已经相当高了(所以无论如何你都不想在热循环中做太多事情),并且急切的代码仍然在一个数量级之内(所以在少数情况下你会接受懒惰的开销,但不接受急切的开销)。如果渴望没有为您做任何其他事情,好吧,也许坚持懒惰,但正如我在回答中指出的那样,懒惰评估可以隐藏只有在您打开日志级别以找出问题并发现调试级别日志时才会咬你的错误从来没有工作过。

标签: python logging


【解决方案1】:
logging.error("%.*f", value, precision) # fails

几乎是正确的,但精度必须首先:

logging.error("%.*f", precision, value) # works

助记符:精度的占位符*在float值的占位符f之前,因此精度在float之前提供。


遗憾的是,虽然助记符适用于其他格式化工具,但它适用于每个占位符的开头,而不是结尾。例如,在str.format 中,占位符的自动编号基于每个占位符的左大括号{ 的顺序,因此对于这种情况,您可以:

'{:.{}f}'.format(value, precision)

因为value 的占位符的左大括号位于左大括号占位符之前以确保精度。当然,在这种情况下,您可以通过明确并编号来避免歧义:

'{0:.{1}f}'.format(value, precision)

或命名它们:

'{value:.{precision}f}'.format(precision=precision, value=value)  # Order of kwargs irrelevant

当然,使用 f 字符串,您可以将每个地方使用的名称内联,这样就不会有混淆:

f'{value:.{precision}f}'

而且它比现代 Python 中的任何其他字符串格式化方式都更快,因此对于任何可能在正常使用中实际发出的日志,我都会坚持使用 f-strings。即使对于那些不会发出的日志,记录低于日志级别(并且不会发出)的内容的开销也足够高,因此避免使用临时str 所节省的成本是相当可观的在上下文中最小化,并且除非出现日志记录,否则不会注意到错误的风险足够高,我通常会使用急切的格式化,尤其是对于像这样的更复杂的情况。我肯定被以下形式的代码咬过:

 logging.debug('%s %s', a, b, c)

代码被无条件执行,但从未启用DEBUG 级别日志记录;我第一次需要它来解决问题时,由于占位符计数不匹配,代码中断了。急切的格式化不可能出现类似的问题,无论是现代 f 字符串还是不太现代的 str.format,甚至是急切的 printf 样式格式,如 logging.debug('%s %s' % (a, b, c));即使完全关闭日志记录,您也会看到错误。

【讨论】:

    猜你喜欢
    • 2017-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-10
    相关资源
    最近更新 更多