Можно ли рассчитывать на подсчет ссылок, чтобы закрыть файл в Python?

В этом вопросе " Генерация контрольной суммы MD5 файла" у меня был этот код:

import hashlib
def hashfile(afile, hasher, blocksize=65536):
    buf = afile.read(blocksize)
    while len(buf) > 0:
        hasher.update(buf)
        buf = afile.read(blocksize)
    return hasher.digest()

[(fname, hashfile(open(fname, 'rb'), hashlib.sha256())) for fname in fnamelst]

Меня критиковали за открытие файла внутри списка, и один человек высказал мнение, что если бы у меня был достаточно длинный список, у меня не хватило бы дескрипторов открытого файла. Интерфейсы, которые значительно сократились hashfileгибкость и хэш-файл, принимая аргумент имени файла и используя with были предложены.

Это было необходимо? Я действительно что-то делал не так?

Тестирование этого кода:

#!/usr/bin/python3

import sys
from pprint import pprint # Pretty printing

class HereAndGone(object):
    def __init__(self, i):
        print("%d %x -> coming into existence." % (i, id(self)),
              file=sys.stderr)
        self.i_ = i
    def __del__(self):
        print("%d %x <- going away now." % (self.i_, id(self)),
              file=sys.stderr)

def do_nothing(hag):
    return id(hag)

l = [(i, do_nothing(HereAndGone(i))) for i in range(0, 10)]

pprint(l)

Результаты в этом выводе:

0 7f0346decef0 -> coming into existence.
0 7f0346decef0 <- going away now.
1 7f0346decef0 -> coming into existence.
1 7f0346decef0 <- going away now.
2 7f0346decef0 -> coming into existence.
2 7f0346decef0 <- going away now.
3 7f0346decef0 -> coming into existence.
3 7f0346decef0 <- going away now.
4 7f0346decef0 -> coming into existence.
4 7f0346decef0 <- going away now.
5 7f0346decef0 -> coming into existence.
5 7f0346decef0 <- going away now.
6 7f0346decef0 -> coming into existence.
6 7f0346decef0 <- going away now.
7 7f0346decef0 -> coming into existence.
7 7f0346decef0 <- going away now.
8 7f0346decef0 -> coming into existence.
8 7f0346decef0 <- going away now.
9 7f0346decef0 -> coming into existence.
9 7f0346decef0 <- going away now.
[(0, 139652050636528),
 (1, 139652050636528),
 (2, 139652050636528),
 (3, 139652050636528),
 (4, 139652050636528),
 (5, 139652050636528),
 (6, 139652050636528),
 (7, 139652050636528),
 (8, 139652050636528),
 (9, 139652050636528)]

Очевидно, что каждый HereAndGone объект создается и уничтожается при построении каждого элемента понимания списка. Подсчет ссылок Python освобождает объект, как только на него нет ссылок, что происходит сразу после вычисления значения для этого элемента списка.

Конечно, возможно, некоторые другие реализации Python этого не делают. Требуется ли для реализации Python какая-либо форма подсчета ссылок? Это, конечно, кажется из документации gc Модуль, такой как подсчет ссылок, является основной особенностью языка.

И, если бы я сделал что-то не так, как бы вы предложили мне переписать это, чтобы сохранить краткую ясность понимания списка и гибкость интерфейса, который работает со всем, что может быть прочитано как файл?

1 ответ

Решение

Кто-то указал на раздел "Модель данных" в Справочнике по языку Python, где очень четко сказано: "Не полагайтесь на немедленное завершение объектов, когда они становятся недоступными (поэтому вы всегда должны закрывать файлы явным образом)". Таким образом, это дает понять, что код полагается на поведение, которое не гарантировано.

И даже если бы это было, это все еще хрупко. Он незримо полагается на файл, на который никогда не будет ссылаться структура данных, имеющая циклическую ссылку или время жизни, превышающее хеширование отдельного файла. Кто знает, что может случиться с кодом в будущем и запомнит ли кто-нибудь эту ключевую деталь?

Вопрос в том, что с этим делать. hashfile Функция в вопросе довольно гибкая, и, кажется, стыдно возиться с ее интерфейсом, чтобы взять имя файла и заставить его открыть файл внутри функции и тем самым убить ее гибкость. Я думаю, что минимальное решение заключается в следующем:

Я чувствую, что решение состоит в том, чтобы немного переосмыслить интерфейс и сделать его еще более общим.

def hash_bytestr_iter(hasher, bytesiter, ashexstr=False):
    for block in bytesiter:
        hasher.update(bytesiter)
    return (hasher.hexdigest() if ashexstr else hasher.digest())

def iter_and_close_file(afile, blocksize=65536):
    with afile:
        block = afile.read(blocksize)
        while len(block) > 0:
            yield block

Можно было просто сделать оригинал hashfile использовать переданный в afile как менеджер контекста, но я чувствую, что такого рода обманчиво нарушает ожидания. Это делает hashfile закройте файл, и его имя как бы обещает только то, что он будет вычислять хеш, а не закрывать файл.

И я подозреваю, что есть много случаев, когда у вас есть блоки байтов, и вы хотели бы хэшировать их все, как если бы они были частью непрерывного блока или их потока. Хэширование итератора над блоками байтов является даже более общим, чем хэширование файла.

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

Другие вопросы по тегам