在Python自定义类中实现'with object() as f'的使用

141 投票
3 回答
73075 浏览
提问于 2025-04-16 04:29

我需要在Python中打开一个类似文件的对象(这是通过/dev/进行的串口连接),然后再把它关闭。这在我类的几个方法中会做好几次。我之前的做法是,在构造函数里打开文件,然后在析构函数里关闭它。不过我遇到了一些奇怪的错误,我觉得这可能和垃圾回收有关,我还不太习惯不知道我的对象到底什么时候会被删除。

我之所以这么做,是因为每次打开文件时,我都需要用到tcsetattr这个函数,并且要传入一堆参数,这样反复操作起来很麻烦。所以我想实现一个内部类来处理这些事情,这样我就可以用下面的方式来使用它:
with Meter('/dev/ttyS2') as m:

我在网上查了一下,但没找到一个很好的关于with语法是怎么实现的答案。我看到它使用了__enter__(self)__exit__(self)这两个方法。但我只需要实现这两个方法,就可以使用with语法了吗?还是说还有其他的要求?

有没有示例或者一些文档可以让我看看,了解一下文件对象是怎么实现这个的?

3 个回答

-15

我找到的第一个谷歌搜索结果解释得很简单:

http://effbot.org/zone/python-with-statement.htm

而PEP(Python增强提案)则解释得更准确(不过也更啰嗦):

http://www.python.org/dev/peps/pep-0343/

77

最简单的方法可能是使用标准的Python库模块 contextlib

import contextlib

@contextlib.contextmanager
def themeter(name):
    theobj = Meter(name)
    try:
        yield theobj
    finally:
        theobj.close()  # or whatever you need to do at exit


# usage
with themeter('/dev/ttyS2') as m:
    # do what you need with m
    m.read()

这样做并不会让 Meter 本身变成一个上下文管理器(所以对这个类没有影响),而是用一个工厂函数 themeter 来“装饰”它(这里的“装饰”不是指Python中的“装饰器语法”,而是有点像装饰者设计模式;-))。这个 themeter 是一个上下文管理器(它是通过 contextlib.contextmanager 装饰器从你写的“单个 yield”生成器函数构建而来的)。这样做可以让你更容易地分开进入和退出的条件,避免嵌套等问题。

190

这些方法基本上就是让对象能和 with 语句一起使用所需要的全部内容。

__enter__ 方法中,你需要在打开文件并进行设置后返回这个文件对象。

__exit__ 方法中,你需要关闭这个文件对象。写入文件的代码会放在 with 语句的主体部分。

MODE = 'rb'
class Meter():
    def __init__(self, dev):
        self.dev = dev
    def __enter__(self):
        #ttysetattr etc goes here before opening and returning the file object
        self.fd = open(self.dev, MODE)
        return self
    def __exit__(self, exception_type, exception_value, exception_traceback):
        #Exception handling here
        self.fd.close()

meter = Meter('/dev/tty0')
with meter as m:
    #here you work with the file object.
    m.fd.read()

撰写回答