我怎么知道我的python脚本挂在哪里?

时间:2010-08-09 19:46:47

标签: python debugging

所以我正在调试我的python程序并遇到一个让程序挂起的错误,好像在无限循环中。现在,我之前遇到了无限循环的问题,但是当它挂断时我可以杀死程序并且python吐出一个有用的异常,告诉我当我发送kill命令时程序终止的位置。然而,现在,当程序挂断并且我按下它时,它不会中止但继续运行。我有什么工具可以用来找到挂机吗?我不熟悉分析,但据我所知,分析器只能为您提供有关已成功完成的程序的信息。或者您可以使用分析器来调试此类挂起吗?

14 个答案:

答案 0 :(得分:86)

我们假设您运行的程序为:

python YOURSCRIPT.py

尝试将您的程序运行为:

python -m trace --trace YOURSCRIPT.py

并且在屏幕上印有很多东西时要有耐心。如果你有一个无限循环,它会一直持续(停止问题)。如果它被卡在某处,那么大多数时候你会陷入I / O或者它是一个僵局。

答案 1 :(得分:26)

哇!已有5个答案,没有人提出最明显和最简单的答案:

  1. 尝试找到导致悬挂行为的可重现的测试用例。
  2. 将记录添加到您的代码中。这可以与print "**010"print "**020"等基本相同,也可以通过主要区域进行。
  3. 运行代码。看看它挂在哪里。无法理解为什么?添加更多日志记录(即,如果在** 020和** 030之间,请添加** 023,** 025,** 027等)
  4. 转到3。
  5. 这些天,孩子们用他们的花哨的调试器& IDE ...有时,工程问题最简单地通过提供更多信息的原始工具来解决。

答案 2 :(得分:9)

如果您的程序太大而且太复杂而无法通过pdb单步执行或使用跟踪模块打印每一行,那么您可以尝试从我的8位游戏编程时代开始。从Python 2.5开始,pdb可以使用commands命令将代码与断点相关联。您可以使用它来打印消息并继续运行:

(Pdb) commands 1
(com) print "*** Breakpoint 1 ***"
(com) continue
(com) end
(Pdb)

这将打印一条消息,并在命中断点1时继续运行。为其他一些断点定义类似的命令。

您可以使用它来对代码进行二进制搜索。在代码中的关键位置附加断点并运行它直到它挂起。您可以从上一条消息中判断出它所遇到的最后一个断点。然后,您可以移动其他断点并重新运行以缩小代码中挂起的位置。冲洗并重复。

顺便提一下,在8位微处理器(Commodore 64,Spectrum等)上,您可以将一个值戳到注册表位置,以更改屏幕边框的颜色。我曾经设置了几个断点来用不同的颜色做这个,所以当程序运行时它会给出一个迷幻的彩虹显示,直到它挂起,然后边框会变成一种颜色,告诉你最后一个断点是什么。您还可以通过彩虹中每种颜色的数量来感受不同代码部分的相对性能。有时我会在这些新奇的“Windows”机器中错过这种简洁。

答案 3 :(得分:7)

我写了一个模块,打印出在一个地方挂了10秒钟的线程。 hanging_threads.py

以下是输出示例:

--------------------    Thread 5588     --------------------
  File "C:\python33\lib\threading.py", line 844, in _exitfunc
        t.join()
  File "C:\python33\lib\threading.py", line 743, in join
        self._block.wait()
  File "C:\python33\lib\threading.py", line 184, in wait
        waiter.acquire()

当您忘记将另一个线程设置为守护程序时,会发生在主线程的退出处。

答案 4 :(得分:3)

防止这些挂起比调试它们更容易。

首先:for循环非常非常难以在循环不会终止的情况下陷入困境。很难。

第二:while循环相对容易陷入循环。

第一步是检查每个while循环,看看必须是否为while循环。通常,您可以使用while替换for构造,并通过重新考虑循环来纠正您的问题。

如果您无法用while替换for循环,那么您只需要证明while语句中的表达式必须每次都会发生变化循环。这并不难证明。

  1. 查看循环中的所有条件。请将此 T

  2. 查看循环体中的所有逻辑分支。有没有办法在不改变条件的情况下完成循环, T

    • 是?那是你的错。那条逻辑路径是错误的。

    • 没有?非常好,该循环必须终止。

答案 5 :(得分:3)

如果你的程序太复杂而不能简单地跟踪所有函数,你可以尝试运行它并手动将lptrace之类的跟踪程序附加到它上面。它有点像strace - 它打印你的程序所做的每个函数调用。以下是如何调用它:

python lptrace -p $STUCK_PROGRAM_PID

请注意,lptrace需要运行gdb。

答案 6 :(得分:2)

没有什么比好老pdb

更像了
import pdb
pdb.run('my_method()',globals(),locals())

然后点击(n)转到下一个命令,(s)进入。请参阅文档以获取完整参考。 一步一步地按照你的程序进行操作,你可能已经足够快了。

答案 7 :(得分:2)

您也可以尝试http://code.activestate.com/recipes/576515-debugging-a-running-python-process-by-interrupting/。只要Python进程没有屏蔽信号,它就应该工作,即使Ctrl-C不起作用,通常情况也是如此。

答案 8 :(得分:2)

从Python 3.3开始,有一个内置的faulthandler模块:

import faulthandler
faulthandler.enable()

答案 9 :(得分:2)

多线程守护进程;使用 pyrasite 检查正在运行的程序

我有一个多线程守护进程,有时会在几个小时后卡住,有时在几周后卡住。通过调试器运行它是不可行的,甚至可能没有帮助,因为调试多线程或多进程程序可能很痛苦。通过跟踪运行它可能会在它卡住之前填满千兆字节,如果不是太字节的话。守护进程第二次出现挂起时,我想立即知道它在哪里,无需重新启动它,添加检查代码,通过调试器运行它,并等待数小时、数天或数周待查情况再次挂起。

我被 pyrasite 拯救了,它允许用户连接到正在运行的 Python 进程并以交互方式检查帧(示例受 this gist 启发):

$ pyrasite-shell 1071  # 1071 is the Process ID (PID)
Pyrasite Shell 2.0
Connected to '/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/bin/python3.8 /opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/bin/satpy_launcher.py -n localhost /opt/pytroll/pytroll_inst/config/trollflow2.yaml'                                                                                               
Python 3.8.6 | packaged by conda-forge | (default, Dec 26 2020, 05:05:16)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(DistantInteractiveConsole)

>>> import sys
>>> sys._current_frames()
{139652793759488: <frame at 0x7f034b2c9040, file '<console>', line 1, code <module>>, 139653520578368: <frame at 0x7f034b232ac0, file '/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/pyresample/spherical.py', line 112, code __init__>}

第一帧没有信息;那是我们自己的硫磷矿壳。然而,第二帧显示当前我们的脚本卡在第 112 行的 pyresample.spherical 模块中。我们可以使用回溯模块来获得完整的回溯:

>>> import traceback
>>> traceback.print_stack(list(sys._current_frames().values())[1])
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/bin/satpy_launcher.py", line 80, in <module>
    main()
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/bin/satpy_launcher.py", line 75, in main
    run(prod_list, topics=topics, test_message=test_message,
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/trollflow2/launcher.py", line 152, in run
    proc.start()
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/multiprocessing/process.py", line 121, in start
    self._popen = self._Popen(self)
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/multiprocessing/context.py", line 224, in _Popen
    return _default_context.get_context().Process._Popen(process_obj)
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/multiprocessing/context.py", line 277, in _Popen
    return Popen(process_obj)
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/multiprocessing/popen_fork.py", line 19, in __init__
    self._launch(process_obj)
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/multiprocessing/popen_fork.py", line 75, in _launch
    code = process_obj._bootstrap(parent_sentinel=child_r)
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/multiprocessing/process.py", line 315, in _bootstrap
    self.run()
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/multiprocessing/process.py", line 108, in run
    self._target(*self._args, **self._kwargs)
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/trollflow2/launcher.py", line 268, in process
    cwrk.pop('fun')(job, **cwrk)
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/trollflow2/plugins/__init__.py", line 403, in covers
    cov = get_scene_coverage(platform_name, start_time, end_time,
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/trollflow2/plugins/__init__.py", line 425, in get_scene_coverage
    return 100 * overpass.area_coverage(area_def)
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/trollsched/satpass.py", line 242, in area_coverage
    inter = self.boundary.contour_poly.intersection(area_boundary)
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/pyresample/spherical.py", line 494, in intersection
    return self._bool_oper(other, -1)
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/pyresample/spherical.py", line 475, in _bool_oper
    inter, edge2 = edge1.get_next_intersection(narcs2, inter)
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/pyresample/spherical.py", line 326, in get_next_intersection
    return None, None
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/pyresample/spherical.py", line 298, in intersection
    return None
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/pyresample/spherical.py", line 264, in intersections
    return (SCoordinate(lon, lat),
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/pyresample/spherical.py", line 62, in cross2cart
    return res
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/pyresample/spherical.py", line 112, in __init__
    self.cart = np.array(cart)

并且我们可以使用 Python 内省的所有功能来检查堆栈,以帮助我们重建卡住的情况。

答案 10 :(得分:0)

我自己没有使用它,但我听说Eric IDE很好并且有一个很好的调试器。这也是我所知道的唯一具有Python调试器的IDE

答案 11 :(得分:0)

如果你的程序有多个线程,它可能会忽略ctrl-c,因为一个线程连接到ctrl-c处理程序,但实时(失控?)线程对它是聋。 CPython中的GIL(全局解释器锁)意味着通常一次只能运行一个线程。 我想我使用this

解决了我(也许)类似的问题

答案 12 :(得分:0)

i = 0
for t in threading.enumerate():
    if i != 0:# and t.getName() != 'Thread-1':
        print t.getName()
        t._Thread__stop()
    i += 1

一旦知道了线程的名称;开始重新执行您的脚本并将其过滤掉,而不是阻止它们被中止。 i = 0条件阻止主线程被中止。

我建议您浏览并命名所有线程;如: 线程(target = self.log_sequence_status,name =&#39; log status&#39;)

此代码应放在启动失控过程的主程序的末尾

答案 13 :(得分:-8)

哇!似乎你一次性添加了这么多代码而没有测试它,你不能说在程序开始挂起之前添加了什么代码......(最有可能导致问题)。

说真的,你应该用小步骤编码并单独测试每一个(理想情况下做TDD)。

对于发现正在运行的python代码并且ctrl-c不起作用的确切问题,我将尝试一个原始的猜测:你是否使用了一些except:隐约地捕获所有异常。如果你在循环中这样做(并在管理异常后继续循环),这很可能是ctrl-c不起作用的原因:它被这个异常所捕获。更改为except Exception:并且它不应再被捕获(ctrl + c还有其他可能像其他海报建议的那样像线程管理一样工作,但我相信上述原因更有可能)。

  

异常KeyboardInterrupt

Raised when the user hits the interrupt key (normally Control-C or Delete). 
     

在执行期间,定期检查中断。   当内置函数input()或raw_input()为时,键入中断   等待输入也会引发此异常。异常继承   来自BaseException,以免被代码意外捕获   捕获异常,从而阻止解释器退出。

Changed in version 2.5: Changed to inherit from BaseException.