Подтвердить что ты не робот

Подклассификация файловых объектов (для продолжения операций открытия и закрытия) в python 3

Предположим, что я хочу расширить встроенную абстракцию файла с помощью дополнительных операций в open и close. В 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

Теперь я ищу обновление программы до Python 3, в которой open - это функция factory, которая может возвращать экземпляр любого из нескольких разных классов из модуля io в зависимости от того, как он называется. Я мог бы в принципе подклассировать их всех, но это утомительно, и мне пришлось бы переопределить диспетчерство, которое open делает. (В Python 3 различие между двоичными и текстовыми файлами имеет большее значение, чем в 2.x, и мне нужны оба.) Эти объекты будут переданы в библиотечный код, который может сделать что угодно с ними, поэтому идиома создания "файла-подобного" класса с утиным типом, который обертывает возвращаемое значение open и пересылает необходимые методы, будет наиболее подробным.

Может ли кто-нибудь предложить подход 3.x, который включает в себя как можно больше дополнительного шаблона за пределами отображаемого кода 2.x?

4b9b3361

Ответ 1

Вместо этого вы можете использовать диспетчер контекста. Например, этот:

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 в любом случае предпочтительнее для файловых объектов (и других ресурсов).

Ответ 2

tl; dr Используйте контекстный менеджер. См. Нижнюю часть этого ответа для важных предупреждений о них.


В Python файлы стали более сложными 3. Хотя существуют некоторые методы, которые могут использоваться в обычных пользовательских классах, эти методы не работают со встроенными классами. Один из способов заключается в смешивании в желаемом классе до его запуска, но для этого нужно знать, что должен быть первым классом mix-in:

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 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, чтобы убедиться, что вы не утечка ресурсов.

Ответ 3

У меня была аналогичная проблема и требование поддержки как 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)

Он передает все вызовы основным объектам файла и может быть инициализирован из открытого файла или из имени файла. Также работает в качестве менеджера контекста. Вдохновленный этим ответом.