在python 3中继承文件对象(以扩展打开和关闭操作)

zwo*_*wol 20 python python-3.x

假设我想在内部openclose时间使用额外的操作来扩展内置文件抽象.在Python 2.7中,这适用于:

class ExtFile(file):
    def __init__(self, *args):
        file.__init__(self, *args)
        # extra stuff here

    def close(self):
        file.close(self)
        # extra stuff here
Run Code Online (Sandbox Code Playgroud)

现在我正在考虑将程序更新为Python 3,其中open是一个工厂函数,它可能会从io模块中返回任何几个不同类的实例,具体取决于它的调用方式.我原则上可以将它们全部子类化,但这很乏味,而且我必须重新实现这样open做的调度.(在Python 3中,二进制文件和文本文件之间的区别比2.x更重要,我需要两者.)这些对象将被传递给库代码,这些代码可能与它们做任何事情,所以这些成语制作一个"类文件"的duck-typed类来包装返回值open和转发必要的方法将是最冗长的.

任何人都可以建议3.x方法,除了显示的2.x代码之外,尽可能少的额外样板吗?

pok*_*oke 16

您可以只使用上下文管理器.例如这一个:

class SpecialFileOpener:
    def __init__ (self, fileName, someOtherParameter):
        self.f = open(fileName)
        # do more stuff
        print(someOtherParameter)
    def __enter__ (self):
        return self.f
    def __exit__ (self, exc_type, exc_value, traceback):
        self.f.close()
        # do more stuff
        print('Everything is over.')
Run Code Online (Sandbox Code Playgroud)

然后你可以像这样使用它:

>>> with SpecialFileOpener('C:\\test.txt', 'Hello world!') as f:
        print(f.read())

Hello world!
foo bar
Everything is over.
Run Code Online (Sandbox Code Playgroud)

with无论如何,对文件对象(和其他资源)首选使用上下文块.


Eth*_*man 12

tl; dr 使用上下文管理器.有关它们的重要注意事项,请参阅本答案的底部.


Python 3中的文件变得更加复杂.虽然有些方法可以在普通用户类上使用,但这些方法不适用于内置类.一种方法是在实现它之前混合所需的类,但这需要知道混合类应该是什么:

class MyFileType(???):
    def __init__(...)
        # stuff here
    def close(self):
        # more stuff here
Run Code Online (Sandbox Code Playgroud)

因为有这么多种类,多的可能可能在未来(可能性很小,但有可能)被加入,我们不知道肯定这将直到返回调用open,这种方法是行不通的.

另一种方法是更改​​我们的自定义类型以获取返回的文件___bases__,并将返回的实例的__class__属性修改为我们的自定义类型:

class MyFileType:
    def close(self):
        # stuff here

some_file = open(path_to_file, '...') # ... = desired options
MyFileType.__bases__ = (some_file.__class__,) + MyFile.__bases__
Run Code Online (Sandbox Code Playgroud)

但这会产生

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __bases__ assignment: '_io.TextIOWrapper' deallocator differs from 'object'
Run Code Online (Sandbox Code Playgroud)

另一种可以与纯用户类一起使用的方法是直接从返回的实例的类中动态创建自定义文件类型,然后更新返回的实例的类:

some_file = open(path_to_file, '...') # ... = desired options

class MyFile(some_file.__class__):
    def close(self):
        super().close()
        print("that's all, folks!")

some_file.__class__ = MyFile
Run Code Online (Sandbox Code Playgroud)

但又一次:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __class__ assignment: only for heap types
Run Code Online (Sandbox Code Playgroud)

因此,它看起来像是在Python 3中可以使用的最佳方法,幸运的是它也可以在Python 2中工作(如果你想在两个版本上使用相同的代码库,那么它很有用)就是拥有一个自定义上下文管理器:

class Open(object):
    def __init__(self, *args, **kwds):
        # do custom stuff here
        self.args = args
        self.kwds = kwds
    def __enter__(self):
        # or do custom stuff here :)
        self.file_obj = open(*self.args, **self.kwds)
        # return actual file object so we don't have to worry
        # about proxying
        return self.file_obj
    def __exit__(self, *args):
        # and still more custom stuff here
        self.file_obj.close()
        # or here
Run Code Online (Sandbox Code Playgroud)

并使用它:

with Open('some_file') as data:
    # custom stuff just happened
    for line in data:
        print(line)
# data is now closed, and more custom stuff
# just happened
Run Code Online (Sandbox Code Playgroud)

需要记住的一点是:任何未处理的异常__init____enter__将阻止__exit__运行,因此在这两个位置,您仍然需要使用try/ except和/或try/ finally惯用法来确保不泄漏资源.


Lev*_*sky 7

我有类似的问题,并且要求支持Python 2.x和3.x. 我所做的与以下(当前完整版)类似:

class _file_obj(object):
    """Check if `f` is a file name and open the file in `mode`.
    A context manager."""
    def __init__(self, f, mode):
        if isinstance(f, str):
            self.file = open(f, mode)
        else:
            self.file = f
        self.close_file = (self.file is not f)
    def __enter__(self):
        return self
    def __exit__(self, *args, **kwargs):
        if (not self.close_file):
            return  # do nothing
        # clean up
        exit = getattr(self.file, '__exit__', None)
        if exit is not None:
            return exit(*args, **kwargs)
        else:
            exit = getattr(self.file, 'close', None)
            if exit is not None:
                exit()
    def __getattr__(self, attr):
        return getattr(self.file, attr)
    def __iter__(self):
        return iter(self.file)
Run Code Online (Sandbox Code Playgroud)

它将所有调用传递给底层文件对象,并可以从打开的文件或文件名初始化.也可用作上下文管理器.灵感来自这个答案.

  • @ThorSummoner我认为"不仅仅是要求"与"不解决问题"不一样.鉴于已经有答案,我认为添加扩展版本不会有害.答案解释了代码的附加功能.谢谢你的反馈. (5认同)