13

众所周知,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()

这个类被另一个类使用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()

我的问题:当我在mydevice.pywith__enter____exit__方法中实现上下文管理器时,如何处理这个类myclass.py?我需要做类似的事情

def __init__(self, device):
    with mydevice.MyWrapper(device):
        ???

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

4

3 回答 3

17

我建议使用 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()

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

with遇到该语句时,该open_device()函数将执行到该yield语句。语句中的值在作为可选子句yield目标的变量中返回,在本例中为. 此后,您可以像使用普通 Python 对象一样使用该值。当控制通过任何路径从块中退出时——包括抛出异常——函数的剩余部分将执行。aswrapper_objectopen_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)

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

于 2013-03-15T08:37:48.913 回答
4

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

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

def processing(self, value):
     with self.device:
        if value:
            something_else()

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

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

于 2013-03-15T08:21:15.903 回答
0

我不太确定你在问什么。上下文管理器实例可以是类成员 - 您可以在任意多个with子句中重复使用它,并且每次都会调用__enter__()and方法。__exit__()

因此,一旦您将这些方法添加到 中MyWrapper,您就可以像上面那样构建它MyClass。然后你会做类似的事情:

def my_method(self):
    with self.mydevice:
        # Do stuff here

这将调用您在构造函数中创建的实例的__enter__()and方法。__exit__()

但是,该with子句只能跨越一个函数 - 如果您with在构造函数中使用该子句,那么它将__exit__()在退出构造函数之前调用。如果您想这样做,唯一的方法是使用__del__(),正如您已经提到的那样,它有其自身的问题。您可以在需要时打开和关闭设备,with但我不知道这是否满足您的要求。

于 2013-03-15T08:21:52.143 回答