Обертка класса вокруг файла - правильный способ закрыть дескриптор файла, когда больше не ссылаются

У меня есть класс, который обертывает некоторые функции обработки файлов, которые мне нужны. Другой класс создает экземпляр filehandler и использует его в течение неопределенного промежутка времени. В конце концов, caller уничтожается, что уничтожает единственную ссылку на filehandler.

Каков наилучший способ закрыть filehandler файл?

В настоящее время я использую __del__(self), но после просмотра несколько разных questions и статьи, у меня сложилось впечатление, что это считается плохой вещью.

class fileHandler:
    def __init__(self, dbf):
        self.logger = logging.getLogger('fileHandler')
        self.thefile = open(dbf, 'rb')
    def __del__(self):
        self.thefile.close()

Это соответствующий бит обработчика. Вся цель класса состоит в том, чтобы абстрагироваться от деталей работы с основным файловым объектом, а также во избежание ненужного прочтения всего файла в памяти. Тем не менее, часть обработки базового файла закрывает его, когда объект выходит из области видимости.

caller не должен знать или заботиться о деталях, участвующих в filehandler. Задачей filehandler является освобождение любых необходимых ресурсов, когда они выпадают из сферы действия. Это одна из причин, по которой она была абстрагирована в первую очередь. Таким образом, мне кажется, что мы сталкиваемся с перемещением кода filehandler в вызывающий объект или с негерметичной абстракцией.

Мысли?

Ответ 1

__del__ само по себе не является плохим. Вам просто нужно быть особенно осторожным, чтобы не создавать ссылочные циклы в объектах с __del__. Если вам нужно создавать циклы (родительский объект относится к дочернему элементу, который ссылается на родительский), тогда вы захотите использовать модуль weakref.

Итак, __del__ в порядке, просто будьте осторожны с цилиндрическими ссылками.

Сбор мусора. Важным моментом здесь является то, что когда объект выходит за рамки, он может быть собран в мусор, и на самом деле это будет сбор мусора... но когда? Нет гарантии того, когда, и разные реализации Python имеют разные характеристики в этой области. Поэтому для управления ресурсами вам лучше быть явным и добавлять .close() в свой filehandler или, если ваше использование совместимо, добавьте методы __enter__ и __exit__.

Здесь описаны методы __enter__ и __exit__. Одна из них очень хороша в том, что __exit__ вызывается даже тогда, когда происходят исключения, поэтому вы можете рассчитывать или ваши ресурсы закрываются грациозно.

Ваш код, расширенный для __enter__/__exit__:

class fileHandler:
    def __init__(self, dbf):
        self.logger = logging.getLogger('fileHandler')
        self.thefilename = dbf
    def __enter__(self):
        self.thefile = open(self.thefilename, 'rb')
        return self
    def __exit__(self, *args):
        self.thefile.close()

Обратите внимание, что файл открывается в __enter__ вместо __init__ - это позволяет вам создать объект filehandler один раз, а затем использовать его, когда вам нужно в with, не воссоздавая его:

fh = filehandler('some_dbf')
with fh:
    #file is now opened
    #do some stuff
#file is now closed
#blah blah
#need the file again, so
with fh:
    # file is open again, do some stuff with it
#etc, etc 

Ответ 2

Как вы уже написали, класс не делает файл более надежным. Если вы просто отбросите экземпляр обработчика файлов на полу, файл не закроется, пока объект не будет уничтожен. Это может произойти немедленно или может не произойти до тех пор, пока объект не будет собран мусором, но простое удаление простого файлового объекта на пол закроет его так же быстро. Если единственная ссылка на thefile находится внутри вашего объекта класса, то, когда filehandler является сборщиком мусора, thefile также будет собираться мусором и, следовательно, одновременно закрываться.

Правильный способ использования файлов - использовать оператор with:

with open(dbf, 'rb') as thefile:
    do_something_with(thefile)

это гарантирует, что thefile всегда будет закрыт при выходе из предложения with. Если вы хотите обернуть ваш файл в другой объект, вы можете сделать это тоже, определив __enter__ и __exit__:

class FileHandler:
    def __init__(self, dbf):
        self.logger = logging.getLogger('fileHandler')
        self.thefile = open(dbf, 'rb')
    def __enter__(self):
        return self
    def __exit__(self, exc_type, exc_value, traceback):
        self.thefile.close()

и тогда вы можете сделать:

with FileHandler(dbf) as fh:
    do_something_with(fh)

и убедитесь, что файл быстро закрывается.