ValueError:使用** generator ** over ** list **时关闭文件的I / O操作

时间:2016-09-23 08:43:05

标签: python list python-2.7 csv generator

我有以下两个函数从csv文件中提取数据,一个返回一个列表,另一个返回一个生成器:

列表:

def data_extraction(filename,start_line,node_num,span_start,span_end):
    with open(filename, "r") as myfile:
        file_= csv.reader(myfile, delimiter=',')  #extracts data from .txt as lines
        return [filter(lambda a: a != '', row[span_start:span_end]) \
        for row in itertools.islice(file_, start_line, node_num+1)]

发电机:

def data_extraction(filename,start_line,node_num,span_start,span_end):
    with open(filename, "r") as myfile:
        file_= csv.reader(myfile, delimiter=',')  #extracts data from .txt as lines            
        return (itertools.ifilter(lambda a: a != '', row[span_start:span_end]) \
                for row in itertools.islice(file_, start_line, node_num+1))

我通过调用以下函数之一来启动程序以提取数据。 下一行是:print [x in data]

当我使用返回列表的函数时,一切正常,当我使用我得到的生成器时:ValueError: I/O operation on closed file

我从其他问题中收集到这是因为with open函数data_extraction可能导致returns语句丢失。

问题是:是否有一种解决方法可以保留一个独立的函数来提取数据,这样我就不必将所有代码都放在一个函数中?其次,我能够重置发生器多次使用它吗?

希望将生成器保留在列表中的原因是我正在处理大型数据集。

1 个答案:

答案 0 :(得分:2)

请注意,with语句会在文件末尾关闭文件。这意味着无法再从中读取数据。

列表版本实际读入所有数据,因为必须创建列表元素。

在您实际从生成器获取数据之前,生成器版本决不会读取任何数据。由于您在关闭文件后执行此操作,因此生成器将失败,因为它然后尝试获取它。

您只能通过实际读取数据来避免这种情况,例如:正如您通过创建列表所做的那样。试图不保留所有数据(生成器)但仍希望拥有所有数据(关闭文件)没有意义。

另一种方法是每次打开文件进行读取 - 文件对象就像其值的生成器一样。如果要避免重复过滤代码,可以为此创建一个包装器:

直接的方法是将发电机返回功能转换为发电机功能本身:

def data_extraction(filename,start_line,node_num,span_start,span_end):
    with open(filename, "r") as myfile:
        file_= csv.reader(myfile, delimiter=',')  #extracts data from .txt as lines            
        for item in (itertools.ifilter(lambda a: a != '', row[span_start:span_end]) \
            for row in itertools.islice(file_, start_line, node_num+1)):
            yield item

这有一个问题:with语句只会在生成器耗尽或收集后关闭。这会带来与打开文件相同的情况,您也必须完成此操作。

更安全的替代方案是使用过滤器生成器并将其提供给文件内容:

def data_extraction(file_iter, start_line, node_num, span_start, span_end):
    file_= csv.reader(file_iter, delimiter=',')  #extracts data from .txt as lines            
    for item in (itertools.ifilter(lambda a: a != '', row[span_start:span_end]) \
        for row in itertools.islice(file_, start_line, node_num+1)):
        yield item

# use it as such:
with with open(filename, "r") as myfile:
    for line in data_extraction(mayflies):
        # do stuff

如果您经常需要这个,您还可以通过实现上下文管理器协议来创建自己的类。然后,可以在with语句中使用此语句,而不是open

class FileTrimmer(object):
    def __init__(self, filename, start_line, node_num, span_start, span_end):
        # store all attributes on self

    def __enter__(self):
        self._file = open(self.filename, "r")
        csv_reader = csv.reader(self._file, delimiter=',')  #extracts data from .txt as lines         
        return (
            itertools.ifilter(
                lambda a: a != '',
                row[self.span_start:self.span_end])
                for row in itertools.islice(
                    csv_reader,
                    self.start_line,
                    self.node_num+1
        ))

    def __exit__(self, *args, **kwargs):
         self._file.close()

您现在可以像这样使用它:

with FileTrimmer('/my/file/location.csv', 3, 200, 5, 10) as csv_rows:
    for row in csv_rows:  # row is an *iterator* over the row
        print('<', '>, <'.join(row), '>')