Вернуть дескрипторы файлов, открытые с помощью open?

Я создаю программное обеспечение, в котором я хочу принимать сжатые файлы. Поскольку файлы читаются/записываются везде, я создал служебную функцию для открытия файлов, которая обрабатывает открытие/закрытие для меня для некоторых типов сжатых файлов.

Пример кода:

def return_file_handle(input_file, open_mode="r"):
    """ Handles compressed and uncompressed files. Accepts open modes r/w/w+ """

    if input_file.endswith(".gz")
        with gzip.open(input_file, open_mode) as gzipped_file_handle:
            return gzipped_file_handle

Проблема в том, что при использовании этого кода дескриптор файла закрывается при возврате функции. Можно ли делать с with open то, что я хочу, или мне нужно самому справляться с закрытием?

Добавьте это к приведенному выше коду, чтобы получить минимальный неработающий пример:

for line in return_file_handle(input_bed, "rb"):
    print line

Создайте сжатый текстовый файл с помощью:

echo "hei\nder!" | gzip - > test.gz

Сообщение об ошибке:

Traceback (most recent call last):
  File "check_bed_against_blacklist.py", line 26, in <module>
    check_bed_against_blacklist("test.gz", "bla")
  File "check_bed_against_blacklist.py", line 15, in check_bed_against_blacklist
    for line in return_file_handle(input_bed, "r"):
ValueError: I/O operation on closed file.

person The Unfun Cat    schedule 03.03.2015    source источник


Ответы (5)


Попробуйте в качестве генератора:

def return_file_handle(input_file, open_mode="r"):
    """
    Handles compressed and uncompressed files. Accepts open modes r/w/w+
    """
    # compressed
    if input_file.endswith(".gz"):
        with gzip.open(input_file, open_mode) as gzipped_file_handle:
            yield gzipped_file_handle
    else:
        with open(input_file, open_mode) as normal_fh:
            yield normal_fh

Когда вы называете это:

for line in return_file_handle("file.gz"):
    print(line.read())

Или составить генератор с помощью новый синтаксис yield from Python:

def each_line(fh):
    for l in fh:
        yield from l

И вызов его:

for each in each_line(return_file_handle(fh)):
    print(each)

с чистым закрытием файла в конце цикла for.

person salparadise    schedule 03.03.2015
comment
Это классно. Я не понимал, насколько простыми были генераторы в питоне. Мне нравится этот ответ больше всего, так как он закрывает файл для вас, как только вы закончите его читать. - person Alejandro; 03.03.2015
comment
Осторожно - это может быть не то, что вы думаете. return_file_handle возвращает генератор в файл, а НЕ в строки, поэтому цикл for запускается только один раз. Значение, если вы хотите обработать его построчно: для файла в return_file_handle(file.gz): для строки в файле.readlines(): напечатать строку - person ronathan; 11.07.2019

Лучший способ, о котором я могу думать, - это передать функцию в качестве параметра, который принимает открытый fd:

def work(fd):
    for line in fd:
        print line

def work_with_file_handle(input_file, func, open_mode="r"):
   if input_file.endswith(".gz")
       with gzip.open(input_file, open_mode) as gzipped_file_handle:
           func(gzipped_file_handle)

work_with_file_handle('xxx.gz', work)
person laike9m    schedule 03.03.2015

Избегайте with, если вы хотите вернуть file_handle. Поскольку file_handle будет автоматически закрыт, когда блок with завершит выполнение.

Следующий код - это то, что вы должны использовать:

import gzip
def return_file_handle(input_file, open_mode="rb"):
    if input_file.endswith(".gz"):
        gzipped_file_handle = gzip.open(input_file, open_mode)
        return gzipped_file_handle

for line in return_file_handle('file.txt.gz', "r"):
    print line
person Vinod Sharma    schedule 03.03.2015

Стиль, который вы используете для открытия файла, автоматически закрывает его в конце блока. В этом весь смысл with блочного стиля открытия файлов.

Что вы хотите сделать, это:

gzipped_file_handle = gzip.open(input_file, open_mode)
return gzipped_file_handle

ВНИМАНИЕ. Вам просто нужно быть осторожным, чтобы не забыть закрыть файл после вызова этой функции.

person Alejandro    schedule 03.03.2015

Я бы использовал другой контекстный менеджер

from contextlib import contextmanager

@contextmanager
def return_file_handle(input_file, open_mode="r"):
    """ Handles compressed and uncompressed files. Accepts open modes r/w/w+ """

    if input_file.endswith(".gz")
        with gzip.open(input_file, open_mode) as gzipped_file_handle:
            yield gzipped_file_handle
    else:
        with open(input_file, open_mode) as normal_file:
            yield normal_file
    # Your file will be closed after this


with return_file_handle(file_name, mode) as f:
     pass
person Trinh Hoang Nhu    schedule 16.04.2018