Python脚本在不同的行停止并且不抛出异常

标签 python debugging memory garbage-collection

我有一个 Python 脚本,它对 Word .docx 文件进行了很大的解析工作(并且它对所有以前的文件都有效)。它在运行中突然停止工作而没有抛出任何异常。整个代码被包裹在一个 try-except-clause 中,基本上是这样的:

try:
    report.read_and_postprocess() #: My parsing process (with ALOT of code inside)
    print("Finished...")
except NoObjectForElementError as error:
    print("Known error")
    raise error
except:
    print("Other error")
    raise
finally:
    print("Write this!")

为了定位错误,我尝试使用 print()用于隔离代码停止位置的语句。但是,当我靠近时,停止点会移动到另一个地方。

在我看来,这让我觉得存在内存泄漏,或者可能是我的 Python 环境出现了一些其他限制——但我在调试这个方面不是很熟练。我查看了脚本的内存和 CPU 使用率。在脚本结束时它只消耗了大约 87MB 内存,使得计算机的总内存只从 67% 移动到 68%。 (我只为此查看 Windows 10 任务管理器,因此我不能保证在脚本停止之前内存不会暂时飙升)

我已经重新运行脚本数百次,但从未收到错误消息,除了两次出现此错误:
Python Fatal Error: GC Object already Tracked

我不知道如何继续调试,有人有什么提示吗?它可能是由内存引起的,或者我怎样才能找到?是否存在其他可能导致此问题的 Python 限制? (例如,我已经阅读了关于嵌套 for 循环的 20 个限制 - 但对我来说不应该是这种情况)

更新:它在 report.read_and_postprocess() 期间停止线。

环境信息:
Windows 10、Anaconda 3、Python 3.7。脚本在 anaconda 提示符下运行,并激活了我的环境。

更新:我发现了这个提示:https://stackoverflow.com/a/3443779/6700475
我的代码似乎花费了大量时间来评估正则表达式。基本上这样的行在观察跟踪时很常见:
sre_compile.py(596):     return isinstance(obj, (str, bytes))
sre_compile.py(763):         pattern = p
sre_compile.py(764):         p = sre_parse.parse(p, flags)
 --- modulename: sre_parse, funcname: parse
sre_parse.py(922):     source = Tokenizer(str)
 --- modulename: sre_parse, funcname: __init__
sre_parse.py(225):         self.istext = isinstance(string, str)
sre_parse.py(226):         self.string = string
sre_parse.py(227):         if not self.istext:
sre_parse.py(229):         self.decoded_string = string
sre_parse.py(230):         self.index = 0
sre_parse.py(231):         self.next = None
sre_parse.py(232):         self.__next()
 --- modulename: sre_parse, funcname: __next
sre_parse.py(234):         index = self.index
sre_parse.py(235):         try:
sre_parse.py(236):             char = self.decoded_string[index]
sre_parse.py(240):         if char == "\\":
sre_parse.py(247):         self.index = index + 1
sre_parse.py(248):         self.next = char
sre_parse.py(924):     if pattern is None:
sre_parse.py(925):         pattern = Pattern()
 --- modulename: sre_parse, funcname: __init__
sre_parse.py(77):         self.flags = 0
sre_parse.py(78):         self.groupdict = {}

我运行了跟踪,并且(至少这次)它在 re.match 期间停止了,特别是这一次 - 在循环的第 3 次迭代中:
def is_numeric(text):
    """ Return whether a trimmed string is numeric 

    Numeric formats:
        1
        1.2 (US style)
        1,2 (EU style)
        1,200,340 (US style)
        1 200 340 (other style)
        1.200.340 (eu style?)
        1,200,340.67
        1 200 340,67
        1 200 340.67
        1.200.340,67
        -23
        -23.98
        -2 454 981.21
        + 24
        - 24
        + 24.9183
        12321
        543525,-
        123123,
        12389.
        12 489.
        12 432,
    """
    if len(text) == 0:
        return False

    #: Try float
    try:
        float(text)
    except ValueError:
        pass
    except:
        raise
    else:
        return True

    #: look for all characters that should not be in a number
    if not re.match(r"^[-+0-9., ]*$", text):
        return False

    #: Verify numeric format
    #:  1.200.200,78
    #:  1,200,200.78
    #:  1 200 200,78
    #:  1 200 200.78
    #:  1200200,78
    #:  1200200.78
    #:  - 1200200.78
    #:  + 1.200.200,78
    #:  1200200,-
    #:  -1 200 200,-
    #:  etc.
    variants = ((r",", r"\."),
                (r"\.", r","),
                (r" ", r","),
                (r" ", r"\."))
    for (tho, dec) in variants:
        dec_exp_opt = r"(%s[0-9]*)" % dec
        if dec == ",":
            dec_exp_opt = r"((%s)|(,-))" % dec_exp_opt
        threesep = r"[1-9][0-9]{0,2}(%s[0-9]{3})*(%s)?" % (tho, dec_exp_opt)
        nullsep = r"(([0-9]*(%s[0-9]+)?)|([0-9]+(%s)?))" % (dec, dec_exp_opt)
        expr = r"^([-+][ \t]*)?((%s)|(%s))$" % (threesep, nullsep)

        test = re.match(expr, text) #: IT HAS STOPPED HERE IN ITERATION 3!!
        if test:
            return True

    return False

它此时尝试求解的表达式可能(也可能不是)是随机的:^([-+][ \t]*)?(([1-9][0-9]{0,2}( [0-9]{3})*((((,[0-9]*))|(,-)))?)|((([0-9]*(,[0-9]+)?)|([0-9]+((((,[0-9]*))|(,-)))?))))$对于值 2017-05-29 (最好返回false)。

(糟糕的?)正则表达式可以导致脚本停止而不引发异常是否有意义? re 中是否有一些缓存?可能导致这种情况的模块?

最佳答案

问题最终得到了解决。我最终使用以下提示调试程序:How can I tell where my python script is hanging?

python -m trace --trace YOURSCRIPT.py

使用跟踪模块让我可以找到脚本停止运行的位置。在我的例子中,我运行了一个循环,每次迭代都会进行一堆正则表达式检查。脚本在这些检查期间出现了错误,但每次都在不同的时间点。我不完全确定,但是 Catastrophic Backtracking正如 Mihai Andrei 的回答中所建议的那样,这是一个可能的原因。可以肯定的是,我运行了非常低效的正则表达式检查。我已经重写了脚本的整个正则表达式部分,现在它工作正常。

所以总而言之,我的问题的答案是:
  • 使用 trace 调试脚本模块以找出它停止的位置。这导致我 (2)
  • 一定要避免灾难性回溯 (以及正则表达式中可能存在的内存泄漏[?])

  • 如果有人对为什么没有错误消息的 Python 错误有更深入的解释,或者可以确认正则表达式中的内存泄漏会导致这种情况,请添加到我的答案中!

    关于Python脚本在不同的行停止并且不抛出异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54572087/

    相关文章:

    c - C程序的可变运行时间

    java - 如何获取 Netty 4 应用程序的直接内存快照

    python - Python 什么时候重置线程计数?

    python - 如何检查 python 2.7 中的原始输入是否为整数?

    python - 是否有必要使用 SWIG 在接口(interface)文件中提及包装的 .c 文件的所有函数?

    c++ - gdb中<exp+6>是什么意思?

    ruby-on-rails - Kubernetes 错误同步 pod

    java - 在eclipse中调试。将变量提取到 Excel 工作表

    python - 用 Python ping 一个站点?

    c - C 中的二维数组内存映射