【问题标题】:Tips for debugging list comprehensions?调试列表推导的技巧?
【发布时间】:2011-02-01 21:39:51
【问题描述】:

Python 列表推导很好,但几乎无法调试。你们有什么好的调试技巧/工具吗?

【问题讨论】:

  • 到目前为止的答案似乎是:使用打印调试的特殊情况或根本不使用理解。是否有任何工具可以支持这一点而无需修改您的代码?对我来说,这是一个棘手的问题似乎很奇怪。
  • 这个问题没有内置的解决方案。除了理解弱点之外,python 的内置 map/filter/reduce 在一层或两层嵌套之后无法使用(甚至一层都难以阅读).. 我已经求助于尽其所能允许集合处理管道的库( map/filter/reduce 和朋友)通过在幕后处理记住集合数据的一系列操作。 fluentpy 是其中之一。这种方法绝对是非 Python 的,但远优于仅限于 2 级的 a for comprehension 嵌套

标签: python debugging list list-comprehension


【解决方案1】:

我使用了一个同时打印和返回值的函数:

from pprint import pprint

def debug(msg, item):
    print('\n' + msg + ':')
    pprint(item)
    return item

它对于调试列表/字典理解的任何部分都非常方便:

new_lines = [
    debug('CUR UPDATED LINE', change(line))
    for line
    in debug('ALL LINES', get_lines_from_file(filename))
    if debug('CUR LINE EMPTY?', not_empty(line))
    ]

【讨论】:

  • 我喜欢这个答案!很遗憾在底部看到它,它自然相当于函数式编程中的打印调试。
  • @Rotareti,您是否考虑过以完全相同的方法编辑我 2010 年的答案? ;)
  • @ony 出于某种原因,当我浏览答案时,我没有注意到您使用了类似的方法。我知道这很糟糕,我很抱歉。 :-/
【解决方案2】:

这取决于列表理解。您可以将部分代码移动到另一个函数。这应该是一个更易于调试的干净解决方案。

例子:

[1.0 / i for i in [0, 2, 5, 10]]

可以分为

[f(i) for i in [0, 2, 5, 10]] 

还有一个函数

def f(i):         
    return 1.0 / i  

当您进行调试时,您会发现它会因为 f 的“除零”错误而崩溃,i = 0 的值。

【讨论】:

  • 顺便说一句,您有一个拼写错误(“devision”而不是“division”),我无法为您修复,因为它只是一个两个字符的修复,SE 不会让我做少于 6 个字符的修复程序。
  • 没问题。希望 SE 能找到一种方法让我们自己进行这些类型的修复。
  • 这行得通,但很尴尬:需要定义一个离线的单独函数。那是python..
【解决方案3】:

如果它足够复杂以至于乍一看并不明显,请将其解压缩为多个步骤和/或 for 循环。它显然太复杂了,让它更明确是调试它的最简单方法。额外的好处:您现在可以使用调试器单步执行或添加打印语句!

【讨论】:

  • +1: 列表推导式的 Pythonic 用法是代码实际上变得更清晰、更易于阅读
  • 那种投降的感觉
  • 有时候投降是正确的做法!如果你看不懂,那对于单行来说显然太复杂了。
  • 有时列表理解逻辑并不复杂,但它调用的方法或函数的某处存在错误。
  • 我对这个论点的问题是,我更喜欢列表推导而不是同样复杂的 for 循环的原因是列表推导是声明性的,因此我不太可能把事情搞砸,而不是构建带有 list.append 调用的 for 循环中的列表。老实说,我不明白为什么 Python 不能为列表推导添加调试支持。就今天的情况而言,基本上我写了一个理解,只要它不会给我带来问题,太好了!一旦完成,它就会回到旧的 for 循环。不应该是这样的。
【解决方案4】:

在 Haskell 中我使用类似于:

def trcPV(prompt, value):
    print ("%s%s" % (prompt, str(value)))
    return value

xs = trcPV("xs=", [x for x in range(0,100) if trcPV("check=",(trcPV("x=",x) % 15) in [0,3,5])])

【讨论】:

  • @bob, ermm... 前 3 行是一个提议的函数,类似于 Haskell 中的 trace,它有助于跟踪正在计算的值,同时将未修改的值进一步传递到函数所在的位置。如果你以前有x+y,你可以写trcPV("x=", x) + trcPV("y=", y)
【解决方案5】:

提示:对简单任务(1 或 2 级)使用列表推导。否则,将其明确化更有利于可读性。

【讨论】:

  • 但是如果你对这种类型的理解有错误怎么办?
【解决方案6】:

使用像 pdb 这样的调试器来遍历列表解析或将其分解为完整的 for 循环。

【讨论】:

  • 你能详细说明一下你是如何使用pdb来遍历的吗?您之前是否成功使用过 IDE 调试器(我之前尝试过使用 pdb,但每次都沮丧地放弃了)?
【解决方案7】:

Haskell 列表推导式至少可以(编译器就是这样做的)用映射、连接和过滤器重写。

所以这个 Haskell 例子:

[ x*x | x<-[1..25], even x]

效果如下:

map (\x-> x*x) (filter (even) [1..25])

我预计 Python 将继续保持类似的身份,因此类似的分解也应该在 Python 中产生等效的代码。等效代码应该更容易调试(并且运行效率差不多)。

【讨论】:

  • 确实,只要你使用声明的函数,你就可以在Python中调试map和filter的组合。问题是列表推导更具可读性,而这个答案虽然正确,但基本上是在说“不要使用推导”。目标是学习如何使用推导式并对其进行调试。
  • 在 python3 中,每个 map/filter/reduce 不仅向后添加,而且还需要额外的 list(*),因为 python 认为默认情况下所有东西都应该是生成器。超出单层嵌套完全不可读。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-07
  • 2019-01-09
相关资源
最近更新 更多