具有上下文管理器的Python mixins无法正确解决“超级”调用

时间:2018-12-12 11:39:35

标签: python mixins contextmanager

我正在写一个代表文件的类。此类具有一些可选功能:通常文件存储在内存中,但是有时需要将它们存储在磁盘上,有时我想将它们存储为zip文件,依此类推。我决定使用mixin,可以在其中子类化File类,并在某些情况下需要添加mixins。在这种情况下,读/写文件是一项需要做一些准备和一些清理工作的操作(我需要压缩文件,例如执行一些写操作,然后再次压缩更新版本)。为此,我想使用自定义上下文管理器,以确保即使with语句中间存在异常或return语句,也可以执行这些操作。这是我的代码:

class File(object):

    def read(self):
        return "file content"

class ZipMixin(object):

    def read(self):
        with self:
            return super(ZipMixin, self).read()

    def __enter__(self):
        print("Unzipping")
        return self

    def __exit__(self, *args):
        print("Zipping back")


class SaveMixin(object):

    def read(self):
        with self:
            return super(SaveMixin, self).read()

    def __enter__(self):
        print("Loading to memory")
        return self

    def __exit__(self, *args):
        print("Removing from memory, saving on disk")

class SaveZipFile(SaveMixin, ZipMixin, File):
    pass

f = SaveZipFile()
print(f.read())

但是,输出结果令人失望:

Loading to memory
Loading to memory
Removing from memory, saving on disk
Removing from memory, saving on disk
file content

应为:

Loading to memory from disk
Unzipping
Zipping back
Removing from memory, saving on disk
file content

显然,使用上下文管理器对mixin中的super进行的所有调用都不会“链式”传递给所有mixin,而是两次传递给所有mixin,然后直接传递给超类(省略中间mixin)。我用python 2和3进行了测试,结果相同。怎么了?

2 个答案:

答案 0 :(得分:0)

会发生什么?

“ super”调用按您预期的方式工作,两个mixin的read方法均按预期顺序调用?

但是,您在with self:SaveMixin类的读取方法中都使用了ZipMixin

self在两种情况下都是相同的,导致使用相同的__enter____exit__方法,而与声明类无关。

根据SaveZipFile类的方法解析顺序,使用SaveMixin类的方法:

>>> SaveZipFile.__mro__
(<class '__main__.SaveZipFile'>, <class '__main__.SaveMixin'>, <class '__main__.ZipMixin'>, <class '__main__.File'>, <class 'object'>)

简而言之,将以正确的顺序调用SaveMixin和ZipMixin类的读取方法,但是with self:使用的__enter____exit__方法两次都是SaveMixin类。

如何解决?

似乎with语句对于使用Mixins并不是最佳选择,但是可能的解决方案是使用Decorator Pattern

class File(object):
    def read(self):
        return "file content"

class ZipDecorator(object):
    def __init__(self, inner):
        self.inner = inner

    def read(self):
        with self:
            return self.inner.read()

    def __enter__(self):
        print("Unzipping")
        return self

    def __exit__(self, *args):
        print("Zipping back")


class SaveDecorator(object):
    def __init__(self, inner):
        self.inner = inner

    def read(self):
        with self:
            return self.inner.read()

    def __enter__(self):
        print("Loading to memory")
        return self

    def __exit__(self, *args):
        print("Removing from memory, saving on disk")

class SaveZipFile(object):
    def read(self):
        decorated_file = SaveDecorator(
            ZipDecorator(
                File()
            )
        )

        return decorated_file.read()


f = SaveZipFile()
print(f.read())

输出:

Loading to memory
Unzipping
Zipping back
Removing from memory, saving on disk
file content

答案 1 :(得分:0)

您要传递的self的类型为SaveZipFile。如果您查看SaveZipFile的MRO(方法解析顺序),则类似这样:

            object
         /    |     \ 
SaveMixin  ZipMixin  File
         \    |     /
           SaveZipFile

当您致电with self:时,最终会致电self.__enter__()。并且由于self的类型为SaveZipFile,因此当我们查看该类的MRO路径时(在图形上“向上”,从左到右搜索路径),在第一个路径(在SaveMixin中)。

如果要提供zip并将功能另存为mixins,则最好使用try/finally模式,并让super确定应调用哪个类的方法以及以什么顺序调用:

class File(object):
    def read(self):
        return "file content"

class ZipMixin(object):
    def read(self):
        try:
            print("Unzipping")
            return super(ZipMixin, self).read()
        finally:
            print("Zipping back")

class SaveMixin(object):
    def read(self):
        try:
            print("Loading to memory")
            return super(SaveMixin, self).read()
        finally:
            print("Removing from memory, saving on disk")

class SaveZipFile(SaveMixin, ZipMixin, File):
    pass