23

Suppose I want to extend the built-in file abstraction with extra operations at open and close time. In Python 2.7 this works:

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

    def close(self):
        file.close(self)
        # extra stuff here

Now I'm looking at updating the program to Python 3, in which open is a factory function that might return an instance of any of several different classes from the io module depending on how it's called. I could in principle subclass all of them, but that's tedious, and I'd have to reimplement the dispatching that open does. (In Python 3 the distinction between binary and text files matters rather more than it does in 2.x, and I need both.) These objects are going to be passed to library code that might do just about anything with them, so the idiom of making a "file-like" duck-typed class that wraps the return value of open and forwards necessary methods will be most verbose.

Can anyone suggest a 3.x approach that involves as little additional boilerplate as possible beyond the 2.x code shown?

4

3 回答 3

18

您可以只使用上下文管理器。比如这个:

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.')

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

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

Hello world!
foo bar
Everything is over.

with无论如何,对于文件对象(和其他资源),最好使用上下文块。

于 2013-04-18T14:19:26.110 回答
16

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


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

class MyFileType(???):
    def __init__(...)
        # stuff here
    def close(self):
        # more stuff here

因为类型太多,未来可能会添加更多类型(不太可能,但可能),并且直到调用之后我们才能确定返回哪个类型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__

但这会产生

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

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

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

但同样:

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

因此,看起来在 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

并使用它:

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

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

于 2014-05-22T02:09:50.937 回答
8

我遇到了类似的问题,并且需要同时支持 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)

它将所有调用传递给底层文件对象,并且可以从打开的文件或文件名初始化。也可以作为上下文管理器。受到这个答案的启发。

于 2013-04-18T14:33:44.807 回答