为什么在 ipython 中定义 python 类方法中的信号处理程序不再有效?

发布于 2025-01-20 18:12:14 字数 4991 浏览 3 评论 0原文

我有一大类数据采集方法和设置,我们通常从 ipython 终端使用它们。该类还定义了一个信号处理程序方法 cleanup,该方法应该在用户使用 KeyboardInterrupt 中断数据采集后收集所有待处理的传入数据流并干净地关闭文件(或发生其他一些不可预见的中断)。我通过 signal.signal(signal.SIG*, self.cleanup) 将此信号处理程序方法分配给相应的 signal.SIG* 信号,作为类的一部分 __init__ 方法。这在过去的大约一年中完美地发挥了作用。 5 年了,但自从几个月前全新安装 conda 环境以来似乎就被破坏了。同时,我在一些运行良好的数字或 conda 环境中复制了此内容,在更新后,此内容停止工作。

下面是一个简化的代码示例(在 interrupt_signal_handler_test.py 中定义):

import signal
import time


class InterruptTest(object):

    def __init__(self):
        print("initializing...")

        # print the current SIGINT signal handler
        print (signal.getsignal(signal.SIGINT))

        # assign the cleanup method to the SIGINT signal handler
        signal.signal(signal.SIGINT, self.cleanup)

        # print the current SIGINT signal handler
        print (signal.getsignal(signal.SIGINT))

    def cleanup(self, sigNum, frame):
        print("cleanup was called")
        print(sigNum)
        print(frame)
        raise InterruptedError("Code was interrupted by the user")

    def run(self):
        # print the current SIGINT signal handler
        print (signal.getsignal(signal.SIGINT))
        print("Press Ctrl+C to interrupt the counting.")
        i = 1
        while(True):
            time.sleep(1.0)
            print(i)
            i += 1



首先,当直接通过 python 运行脚本时,它在两种环境中都按预期工作:

$ python interrupt_signal_handler_test.py 
<built-in function default_int_handler>
initializing...
<bound method InterruptTest.cleanup of <__main__.InterruptTest object at 0x1083a1fd0>>
<bound method InterruptTest.cleanup of <__main__.InterruptTest object at 0x1083a1fd0>>
Press Ctrl+C to interrupt the counting.
1
2
3
^Ccleanup was called
2
<frame at 0x1083aac10, file 'interrupt_signal_handler_test.py', line 25, code run>
Traceback (most recent call last):
  File "interrupt_signal_handler_test.py", line 32, in <module>
    t.run()
  File "interrupt_signal_handler_test.py", line 25, in run
    time.sleep(1.0)
  File "interrupt_signal_handler_test.py", line 18, in cleanup
    raise InterruptedError("Code was interrupted by the user")
InterruptedError: Code was interrupted by the user

现在,当运行 (run -i Interrupt_signal_handler_test.py)在“旧”环境中的 ipython 中手动执行相同的代码,我得到以下(期望的)行为:

$ print(signal.getsignal(signal.SIGINT))
<built-in function default_int_handler>

$ t = InterruptTest()
initializing...
<built-in function default_int_handler>
<bound method InterruptTest.cleanup of <__main__.InterruptTest object at 0x10e9c9df0>>

$ t.run()
<bound method InterruptTest.cleanup of <__main__.InterruptTest object at 0x10e9c9df0>>
Press Ctrl+C to interrupt the counting.
1
2
3
^Ccleanup was called
2
<frame at 0x10e9c0640, file '<ipython-input-1-d5369e70ce7e>', line 31, code run>
---------------------------------------------------------------------------
InterruptedError                          Traceback (most recent call last)
....
InterruptedError: Code was interrupted by the user

$ t.run()
<bound method InterruptTest.cleanup of <__main__.InterruptTest object at 0x10e9c9df0>>
Press Ctrl+C to interrupt the counting.
1
2
3
4
^Ccleanup was called
2
<frame at 0x7f8c6d83a000, file '<ipython-input-1-d5369e70ce7e>', line 31, code run>
---------------------------------------------------------------------------
InterruptedError                          Traceback (most recent call last)
....
InterruptedError: Code was interrupted by the user

每次我调用清理方法run() 我的实验。然而,在更新的 python 安装中,我现在得到以下信息:

$ print (signal.getsignal(signal.SIGINT))
<built-in function default_int_handler>

$ t = InterruptTest()
initializing...
<built-in function default_int_handler>
<bound method InterruptTest.cleanup of <__main__.InterruptTest object at 0x1029a04c0>>
# So far so good...

$ t.run()
<built-in function default_int_handler>
Press Ctrl+C to interrupt the counting.
1
2
^C---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Input In [5], in <cell line: 1>()
----> 1 t.run()

Input In [1], in InterruptTest.run(self)
     29 i = 1
     30 while(True):
---> 31     time.sleep(1.0)
     32     print(i)
     33     i += 1

KeyboardInterrupt: 

# Not good: The signal handler was somehow reset to the built-in one

很明显,行为发生了变化 - 无论是否是有意设计的,我无法弄清楚。这是一个已知问题吗?这是故意改变的吗?如果是的话,现在如何将自定义信号处理程序编写为类方法以与交互式使用 ipython 兼容?

另请注意,这与操作系统无关,它发生在 RHELS7 和 OSX 托管的 conda 环境中。

以下是 python 和 ipython 版本:

"old":
  - ipython=7.23.1
  - ipykernel=5.5.5
  - python=3.8.10

"new":
  - ipython=8.1.1
  - ipykernel=6.9.1
  - python=3.8.12

I have a large class of data acquisition methods and settings, which we usually work with from an ipython terminal. The class also defines a signal handler method, cleanup, which is supposed to gather any pending incoming data streams and close files cleanly after a user interrupts the data acquisition with a KeyboardInterrupt (or some other unforeseen interruption occurs). I assigned this signal handler method to the corresponding signal.SIG* signals via signal.signal(signal.SIG*, self.cleanup) as part of the class' __init__ method. This has worked flawlessly for the last ca. 5 years, but seems to be broken since a fresh install of the conda environments a few months ago. I have meanwhile reproduced this on a number or conda envs where things have been working well, and after an update, this stopped working.

Here is a simplified code example (defined in interrupt_signal_handler_test.py):

import signal
import time


class InterruptTest(object):

    def __init__(self):
        print("initializing...")

        # print the current SIGINT signal handler
        print (signal.getsignal(signal.SIGINT))

        # assign the cleanup method to the SIGINT signal handler
        signal.signal(signal.SIGINT, self.cleanup)

        # print the current SIGINT signal handler
        print (signal.getsignal(signal.SIGINT))

    def cleanup(self, sigNum, frame):
        print("cleanup was called")
        print(sigNum)
        print(frame)
        raise InterruptedError("Code was interrupted by the user")

    def run(self):
        # print the current SIGINT signal handler
        print (signal.getsignal(signal.SIGINT))
        print("Press Ctrl+C to interrupt the counting.")
        i = 1
        while(True):
            time.sleep(1.0)
            print(i)
            i += 1



Firstly, when running the script directly through python, it works as intended in both environments:

$ python interrupt_signal_handler_test.py 
<built-in function default_int_handler>
initializing...
<bound method InterruptTest.cleanup of <__main__.InterruptTest object at 0x1083a1fd0>>
<bound method InterruptTest.cleanup of <__main__.InterruptTest object at 0x1083a1fd0>>
Press Ctrl+C to interrupt the counting.
1
2
3
^Ccleanup was called
2
<frame at 0x1083aac10, file 'interrupt_signal_handler_test.py', line 25, code run>
Traceback (most recent call last):
  File "interrupt_signal_handler_test.py", line 32, in <module>
    t.run()
  File "interrupt_signal_handler_test.py", line 25, in run
    time.sleep(1.0)
  File "interrupt_signal_handler_test.py", line 18, in cleanup
    raise InterruptedError("Code was interrupted by the user")
InterruptedError: Code was interrupted by the user

Now, when running (run -i interrupt_signal_handler_test.py) this same code manually in ipython on the "old" environment, I get the following (desired) behavior:

$ print(signal.getsignal(signal.SIGINT))
<built-in function default_int_handler>

$ t = InterruptTest()
initializing...
<built-in function default_int_handler>
<bound method InterruptTest.cleanup of <__main__.InterruptTest object at 0x10e9c9df0>>

$ t.run()
<bound method InterruptTest.cleanup of <__main__.InterruptTest object at 0x10e9c9df0>>
Press Ctrl+C to interrupt the counting.
1
2
3
^Ccleanup was called
2
<frame at 0x10e9c0640, file '<ipython-input-1-d5369e70ce7e>', line 31, code run>
---------------------------------------------------------------------------
InterruptedError                          Traceback (most recent call last)
....
InterruptedError: Code was interrupted by the user

$ t.run()
<bound method InterruptTest.cleanup of <__main__.InterruptTest object at 0x10e9c9df0>>
Press Ctrl+C to interrupt the counting.
1
2
3
4
^Ccleanup was called
2
<frame at 0x7f8c6d83a000, file '<ipython-input-1-d5369e70ce7e>', line 31, code run>
---------------------------------------------------------------------------
InterruptedError                          Traceback (most recent call last)
....
InterruptedError: Code was interrupted by the user

The cleanup method is called every time I run() my experiment. However, on the updated python installation, I now get the following:

$ print (signal.getsignal(signal.SIGINT))
<built-in function default_int_handler>

$ t = InterruptTest()
initializing...
<built-in function default_int_handler>
<bound method InterruptTest.cleanup of <__main__.InterruptTest object at 0x1029a04c0>>
# So far so good...

$ t.run()
<built-in function default_int_handler>
Press Ctrl+C to interrupt the counting.
1
2
^C---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Input In [5], in <cell line: 1>()
----> 1 t.run()

Input In [1], in InterruptTest.run(self)
     29 i = 1
     30 while(True):
---> 31     time.sleep(1.0)
     32     print(i)
     33     i += 1

KeyboardInterrupt: 

# Not good: The signal handler was somehow reset to the built-in one

It is evident that the behavior changed - whether by design or not, I was not able to figure out. Is this a known issue? Was this changed deliberately? And if yes, how would one write custom signal handlers as class methods now to be compatible with using ipython interactively?

Note also that this is not OS-dependent, it happened both on RHELS7 and OSX-hosted conda environments.

Here are the python and ipython versions:

"old":
  - ipython=7.23.1
  - ipykernel=5.5.5
  - python=3.8.10

"new":
  - ipython=8.1.1
  - ipykernel=6.9.1
  - python=3.8.12

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文