Python 包在调用 __del__ 之前卸载

Python package unloaded before __del__ is called

我正在使用 pyvisa 通过 USB 与仪器通信。我能够适当地控制它。由于它是一个高压源,并且在打开高压时忘记它是很危险的,所以我想实现 __del__ 方法以便在代码执行完成时关闭输出。所以基本上我是这样写的:

import pyvisa as visa

class Instrument:
    def __init__(self, resource_str='USB0::1510::9328::04481179::0::INSTR'):
        self._resource_str = resource_str
        self._resource = visa.ResourceManager().open_resource(resource_str)
    
    def set_voltage(self, volts: float):
        self._resource.write(f':SOURCE:VOLT:LEV {volts}')
    
    def __del__(self):
        self.set_voltage(0)

instrument = Instrument()
instrument.set_voltage(555)

问题是它不工作,在终端我得到

$ python3 comunication\ test.py 
Exception ignored in: <function Instrument.__del__ at 0x7f4cca419820>
Traceback (most recent call last):
  File "comunication test.py", line 12, in __del__
  File "comunication test.py", line 9, in set_voltage
  File "/home/superman/.local/lib/python3.8/site-packages/pyvisa/resources/messagebased.py", line 197, in write
  File "/home/superman/.local/lib/python3.8/site-packages/pyvisa/resources/messagebased.py", line 157, in write_raw
  File "/home/superman/.local/lib/python3.8/site-packages/pyvisa/resources/resource.py", line 190, in session
pyvisa.errors.InvalidSession: Invalid session handle. The resource might be closed.

我想发生的事情是在调用我的对象的 __del__ 方法之前 pyvisa 被“删除”。我怎样才能防止这种情况发生?我如何告诉 Python pyvisa 对于 Instrument class 的对象“重要”,因此在所有对象都被销毁之前它不会被卸载?

一般来说,您不能假定 __del__ 会被调用。如果您来自 C++ 等 RAII(资源分配即初始化)语言,Python 不会对析构函数做出类似的保证。

为确保某些操作被逆转,您应该考虑替代方案,例如上下文管理器:

from contextlib import contextmanager

@contextmanager
def instrument(resource_str='USB0::1510::9328::04481179::0::INSTR'):
    ... 
    try:
        ... # yield something
    finally:
        # set voltage of resource to 0 here

你会像这样使用它

with instrument(<something>) as inst:
   ... 
# guaranteed by here to set to 0.

我认为 通常被认为是推荐的解决方案,但上下文管理器并不总是合适,具体取决于应用程序的结构。

另一种选择是在应用程序退出时显式调用清理函数。您可以通过将整个应用程序包装在 try/finally 中并使用 finally 子句进行清理来使其更安全。请注意,如果您不包含 catch,则在执行 finally 后将自动重新引发异常,这可能正是您想要的。示例:

app = Application()
try:
    app.run()
finally:
    app.cleanup()

不过请注意,您可能只是抛出了一个异常。如果异常发生,例如,在通信中,那么您可能无法发送命令来重置输出,因为设备可能希望您完成已经开始的工作。

我在使用另一种连接设备时遇到了同样的安全问题。我无法安全地预测 __del__ 方法的行为,如问题中所讨论的那样 I don't understand this python __del__ behaviour。 我以上下文管理器结尾。在你的情况下它看起来像这样:

    def __enter__(self):
        """
        Nothing to do.
        """
        return self

    def __exit__(self, type, value, traceback):
        """
        Set back to zero voltage.
        """
        self.set_voltage(0)

with Instrument() as instrument:
    instrument.set_voltage(555)

最后,我使用包 atexit 找到了答案 here。这正是我想做的(根据我到目前为止的测试):

import pyvisa as visa
import atexit

class Instrument:
    def __init__(self, resource_str):
        self._resource = visa.ResourceManager().open_resource(resource_str)
        
        # Configure a safe shut down for when the class instance is destroyed:
        def _atexit():
            self.set_voltage(0)
        atexit.register(_atexit) # 
    
    def set_voltage(self, volts: float):
        self._resource.write(f':SOURCE:VOLT:LEV {volts}')
    
instrument = Instrument(resource_str = 'USB0::1510::9328::04481179::0::INSTR')
instrument.set_voltage(555)

这个解决方案的优点是它是用户无关的,用户如何实例化Instrument class并不重要,最后高压将被关闭。