如何使用上下文管理器来避免在python中使用__del__?

Ale*_*lex 10 python contextmanager

众所周知,python __del__方法不应该用于清理重要的东西,因为不能保证调用此方法.另一种方法是使用上下文管理器,如几个线程中所述.

但我不太明白如何重写一个类来使用上下文管理器.详细说明,我有一个简单的(非工作)示例,其中包装器类打开并关闭设备,并且在任何情况下,类的实例都超出其范围(异常等)时将关闭设备.

第一个文件mydevice.py是打开和关闭设备的标准包装类:

class MyWrapper(object):
    def __init__(self, device):
        self.device = device

    def open(self):
        self.device.open()

    def close(self):
        self.device.close()

    def __del__(self):
        self.close()
Run Code Online (Sandbox Code Playgroud)

这个类由另一个类使用myclass.py:

import mydevice


class MyClass(object):

    def __init__(self, device):

        # calls open in mydevice
        self.mydevice = mydevice.MyWrapper(device)
        self.mydevice.open()

    def processing(self, value):
        if not value:
            self.mydevice.close()
        else:
            something_else()
Run Code Online (Sandbox Code Playgroud)

我的问题:当我mydevice.py用with __enter____exit__方法实现上下文管理器时,如何处理这个类myclass.py?我需要做点什么

def __init__(self, device):
    with mydevice.MyWrapper(device):
        ???
Run Code Online (Sandbox Code Playgroud)

但是如何处理呢?也许我忽略了一些重要的事情?或者我可以仅在函数内使用上下文管理器而不是作为类范围内的变量?

pic*_*cer 16

我建议使用contextlib.contextmanager类而不是编写实现__enter__和的类__exit__.以下是它的工作原理:

class MyWrapper(object):
    def __init__(self, device):
        self.device = device

    def open(self):
        self.device.open()

    def close(self):
        self.device.close()

    # I assume your device has a blink command
    def blink(self):
        # do something useful with self.device
        self.device.send_command(CMD_BLINK, 100)

    # there is no __del__ method, as long as you conscientiously use the wrapper

import contextlib

@contextlib.contextmanager
def open_device(device):
    wrapper_object = MyWrapper(device)
    wrapper_object.open()
    try:
        yield wrapper_object
    finally:
        wrapper_object.close()
    return

with open_device(device) as wrapper_object:
     # do something useful with wrapper_object
     wrapper_object.blink()
Run Code Online (Sandbox Code Playgroud)

以符号开头的行称为装饰器.它修改下一行的函数声明.

with遇到声明,该open_device()功能将执行到yield的语句.yield语句中的值在变量中返回,该变量是可选as子句的目标,在本例中wrapper_object.之后您可以像普通Python对象一样使用该值.当控件通过任何路径退出块时- 包括抛出异常 - open_device函数的剩余主体将执行.

我不确定(a)你的包装类是否为较低级别的API添加功能,或者(b)如果它只是你所包含的内容,那么你可以拥有一个上下文管理器.如果(b),那么你可以完全免除它,因为contextlib会为你处理.以下是您的代码可能的样子:

import contextlib

@contextlib.contextmanager
def open_device(device):
    device.open()
    try:
        yield device
    finally:
        device.close()
    return

with open_device(device) as device:
     # do something useful with device
     device.send_command(CMD_BLINK, 100)
Run Code Online (Sandbox Code Playgroud)

99%的上下文管理器使用可以使用contextlib.contextmanager完成.它是一个非常有用的API类(如果您关心这些事情,它实现的方式也是对低级Python管道的创造性使用).


Bre*_*arn 4

问题不在于您在课堂上使用它,而在于您希望以“开放式”方式保留设备:您打开它,然后将其保持打开状态。上下文管理器提供了一种打开某些资源并以相对简短、包含的方式使用它的方法,确保它最终关闭。你现有的代码已经不安全了,因为如果发生一些崩溃,你不能保证你的代码__del__会被调用,所以设备可能会保持打开状态。

在不确切了解该设备是什么以及它如何工作的情况下,很难说更多,但基本想法是,如果可能的话,最好只在需要使用该设备时才打开它,然后立即将其关闭。所以你的processing可能需要改变,更像是:

def processing(self, value):
     with self.device:
        if value:
            something_else()
Run Code Online (Sandbox Code Playgroud)

如果self.device是一个适当编写的上下文管理器,它应该在 中打开设备__enter__并在 中关闭它__exit__。这确保了设备将在块的末尾关闭with

当然,对于某些类型的资源,不可能这样做(例如,因为打开和关闭设备会丢失重要状态,或者是一个缓慢的操作)。如果这是你的情况,那么你就不得不使用它__del__并忍受它的陷阱。基本问题是,没有万无一失的方法可以让设备保持“开放式”,但仍然保证即使在发生一些异常程序故障时它也会关闭。