使用with open打开的文件句柄如何返回?

8
我将创建一个软件,希望能够接受压缩文件。由于文件读写到处都有用到,我创建了一个实用函数来打开文件,它能为某些压缩文件类型处理打开/关闭操作。
示例代码:
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

使用以下步骤创建一个经过gzip压缩的文本文件:

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.
5个回答

16

作为生成器尝试一下:

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())

或者使用Python新的yield from语法来编写生成器:

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

并称之为:

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

在for循环结束时,文件会被干净地关闭。


1
太酷了。我没想到在Python中生成器是如此容易的。我最喜欢这个答案,因为它在你完成读取文件后会自动关闭文件。 - Alejandro
小心-这可能不是你想要的。 "return_file_handle"返回一个生成器到文件,而不是行,所以按照写入的for循环只运行一次。这意味着如果您想逐行处理它: 对于文件中的返回文件句柄(“file.gz”): 对于行中的文件.readlines(): 打印行 - ronathan

4
我能想到的最好方式是将一个接受已打开文件描述符的函数作为参数传递:
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)

3
避免使用`with`,如果你想要返回文件句柄。因为当`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

如果我理解正确的话,在pypy中没有with语句会自动关闭。但在CPython中应该没问题。 - Wyatt Ward

2
我会使用另一个上下文管理器。
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

2

您正在使用的打开文件方式在块结束时自动关闭文件。这就是使用with 块样式打开文件的全部意义所在。

您想要做的是:

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

注意: 在调用这个函数后,你需要小心记得关闭文件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接