Python time.sleep(1) 引发 TypeError?

问题描述 投票:0回答:1

每当我在 python 3 中从子线程中断/终止睡眠父线程时,我都会得到这种奇怪的行为(抛出随机 TypeError 异常),但只有当 python 脚本是由带有

&
的 shell 脚本启动时才会出现这种奇怪的行为使其在后台运行的参数。

所以这是我能想到的可以触发这个问题的最小可重现的Python代码。

User@MSI: ~/test $ cat m.py

import threading
import _thread
import time


def child_thread():
    time.sleep(1)
    print('child interrupting parent')
    _thread.interrupt_main()
    
if __name__ == '__main__':
    t = threading.Thread(target=child_thread, args=())
    t.start()
    print('parent looping')
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        print('caught interruption raised from user or child thread :)')
    except TypeError as e:
        print('why would I ever catch a TypeError?')
        raise
    except Exception as e:
        print('Strange Exception Received ')
        raise

这是一个非常简单的脚本,其中父线程启动子线程,然后永远循环,子线程(1 秒后)中断主线程,应该向主线程引发键盘中断。

现在,如果我使用最后带有背景标志

&
的 bash 脚本调用我的脚本, (下面的输出没有任何意义)

User@MSI: ~/test $ cat ./m.sh
#!/bin/bash
python3 m.py &

User@MSI: ~/test $ ./m.sh
parent looping
child interrupting parent
why would I ever catch a TypeError?
Traceback (most recent call last):
  File "m.py", line 17, in <module>
    time.sleep(1)
TypeError: 'int' object is not callable

下面我显示了我的脚本的输出,当我运行它时它表现完全正常

  1. 从航站楼
  2. 从终端使用 &
  3. 来自 bash 脚本(不带 &)

三个人的行为都符合预期...... (以下输出符合预期)

User@MSI: ~/test $ python3 m.py
parent looping
child interrupting parent
caught interruption raised from user or child thread :)

使用 & 在后台运行时的结果相同 (以下输出符合预期)

User@MSI: ~/test $ python3 m.py &
[1] 5884
parent looping
child interrupting parent
caught interruption raised from user or child thread :)

[1]+  Done   python3 m.py

甚至从一个名为 m.sh 的一行脚本执行 m.py (以下输出符合预期)

User@MSI: ~/test $ cat m.sh
#!/bin/bash
python3 m.py

User@MSI: ~/test $ ./m.sh
parent looping
child interrupting parent
caught interruption raised from user or child thread :)

我完全傻眼了,不知道 time.sleep 和 TypeErrors 与我如何调用我的脚本,特别是从 shell 脚本和后台调用它有什么关系。这是我遇到的最奇怪的错误之一。

如果重要的话,我正在跑步

Python 3.6.12
Debian 9.12

我希望有人能解决这个问题。

编辑:以下是有缺陷版本的字节码比较(从 shell 脚本使用 & 运行)输出 这是好的版本(从终端运行)输出

为了方便比较,这里是字节码的

diff
。唯一的区别是子线程在内存中的位置。

python multithreading shell exception background-process
1个回答
0
投票

这是 CPython 中的一个错误,issue23395

它已在 Python 3.8 中由 PR #7778 修复。 变更日志在 3.8.0 beta 1 的最后一个“库”项目中提到了它,其中写道:

如果 Python 忽略或不处理

_thread.interrupt_main()

 信号,
SIGINT
现在可以避免设置 Python 错误状态。

该修复程序也已在 PR #13541 中反向移植到 Python 3.7,因此您应该会发现它可以在 Python 3.7.3 中重现,然后在 Python 3.7.4 中修复。

Thomas Kluyver 解释为什么它是

TypeError

此操作失败,并显示错误消息“TypeError: 'int' object is not callable”,并且回溯与错误原因完全断开连接,可能是因为它不是来自通常的 Python 堆栈。

问题似乎是,interrupt_main 设置了(在 C 代码中)Handlers[SIGINT].tripped,这仅当处理程序是 Python 函数时才会发生。当 PyErr_CheckSignals() 运行时,它尝试将 Handlers[SIGINT].func 作为 Python 函数调用,但它是一个 Python 整数,从而导致错误。

你是对的,

TypeError
回溯“没有意义”。

仅当 Python 脚本

m.py
从 bash 脚本
m.sh
中的后台任务运行时才会发生这种情况,因为这种方式的作业控制(挂起/恢复任务的能力)被禁用。当作业控制被禁用时,Python会将signum 2处理程序设置为signal.SIG_IGN,从而忽略中断。否则,signum 2 处理程序将为
signal.default_int_handler
,这会引发
KeyboardInterrupt
异常。这不是 CPython 特有的东西,而是 POSIX 的东西(ref)。

您可以通过在 bash 脚本中添加

set -m
(即打开“监视”shell 选项)来确认这一点:

#!/bin/bash
set -m
python3 m.py &

这也应该避免触发错误。

© www.soinside.com 2019 - 2024. All rights reserved.