Python-миксины с контекстными менеджерами не решают "супер" вызов правильно - PullRequest
0 голосов
/ 12 декабря 2018

Я пишу класс, представляющий файл.Этот класс имеет некоторые дополнительные функции: обычно файлы хранятся в памяти, но иногда необходимо хранить их на диске, иногда я хочу сохранить их в виде zip-файлов и так далее.Я решил использовать миксины, где я могу создать подкласс класса File и в случае необходимости добавить миксины, которые мне действительно нужны в некоторых случаях.В такой ситуации чтение / запись в файл - это операция, которая требует некоторой подготовки и некоторой очистки (мне нужно заархивировать файл, выполнить некоторую запись, например, и затем снова заархивировать обновленную версию).Для этой цели я хотел использовать настраиваемые менеджеры контекста, чтобы гарантировать, что эти действия выполняются, даже если есть оператор исключения или возврата в середине оператора with.Вот мой код:

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

Видимо, все вызовы суперв миксинах с менеджерами контекста не передаются «по цепочке» всем миксинам, а два раза сначала в миксины, а затем напрямую в суперкласс (исключая промежуточные миксины).Я проверил это с Python 2 и 3, тот же результат.Что не так?

Ответы [ 2 ]

0 голосов
/ 12 декабря 2018

self, который вы передаете, имеет тип SaveZipFile.Если вы посмотрите на MRO (порядок разрешения методов), равный SaveZipFile, то это будет примерно так:

            object
         /    |     \ 
SaveMixin  ZipMixin  File
         \    |     /
           SaveZipFile

Когда вы вызываете with self:, он в итоге вызывает self.__enter__().А так как self имеет тип SaveZipFile, когда мы смотрим на пути MRO для этого класса («вверх» по графику, ищем пути слева направо), и мы находим соответствие по первому пути (в SaveMixin).

Если вы собираетесь предлагать функции zip и сохранения в виде миксинов, вам, вероятно, лучше использовать шаблон 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
0 голосов
/ 12 декабря 2018

Что происходит?

Вызов "super" работает так, как вы ожидаете, методы 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, но возможное решение - использовать Pattern Decorator :

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