从关闭的文件中恢复生成器| csv阅读器没有将生成器作为openpyxl返回?

时间:2017-06-25 21:37:02

标签: python python-3.x iterator generator openpyxl

我正在阅读xlsx文件(使用openpyxl)和csv(使用csv.reader)。 openpyxl正确返回一个生成器,我可以在从一个区分文件是excel文件还是csv的函数返回后迭代生成器中的值。当我使用csv文件执行相同的操作时会出现问题,您会看到它返回一个生成器,但我无法迭代它,因为在with语句中函数返回后csv文件似乎已关闭。我知道很明显,在with语句填满它的目的之后文件关闭了,但为什么openpyxl工作呢?为什么我仍然可以迭代excel文件的生成器?而且,我的最终问题是,如何使csv.reader的行为与openpyxl在此处的行为相同,即我能够迭代生成器值。

import csv
from openpyxl import load_workbook


def iter_rows(worksheet):
    """
    Iterate over Excel rows and return an iterator of the row value lists
    """
    for row in worksheet.iter_rows():
        yield [cell.value for cell in row]


def get_rows(filename, file_extension):
    """
    Based on file extension, read the appropriate file format
    """
    # read csv
    if file_extension == 'csv':
        with open(filename) as f:
            return csv.reader(f, delimiter=",")

    # read Excel files with openpyxl
    if file_extension in ['xls', 'xlsx']:
        wb2 = load_workbook(filename)
        worksheet1 = wb2[wb2.get_sheet_names()[0]]
        return iter_rows(worksheet1)


# this works properly
rows = get_rows('excels/ar.xlsx', 'xlsx')
print(rows)  # I am: <generator object iter_rows at 0x074D7A58>
print([row for row in rows])  # I am printing each row of the excel file from the generator

# Error: ValueError: I/O operation on closed file
rows = get_rows('excels/ar.csv', 'csv')
print(rows)  # I am: <generator object iter_rows at 0x074D7A58>
print([row for row in rows])  # ValueError: I/O operation on closed file

2 个答案:

答案 0 :(得分:3)

您不会将with语句与openpxyl函数一起使用。但似乎你已经知道了这个问题,即在with块关闭它之后你试图迭代文件处理程序。早点迭代?或者更好的是yield from reader对象:

def get_rows(filename, file_extension):
    """
    Based on file extension, read the appropriate file format
    """
    # read csv
    if file_extension == 'csv':
        with open(filename) as f:
            yield from csv.reader(f, delimiter=",")

    # read Excel files with openpyxl
    if file_extension in ['xls', 'xlsx']:
        wb2 = load_workbook(filename)
        worksheet1 = wb2[wb2.get_sheet_names()[0]]
        yield from iter_rows(worksheet1)

或者,如果您使用的是Python 2:

def get_rows(filename, file_extension):
    """
    Based on file extension, read the appropriate file format
    """
    # read csv
    if file_extension == 'csv':
        with open(filename) as f:
            for row in csv.reader(f, delimiter=",")
                yield row

    # read Excel files with openpyxl
    if file_extension in ['xls', 'xlsx']:
        wb2 = load_workbook(filename)
        worksheet1 = wb2[wb2.get_sheet_names()[0]]
        for row in iter_rows(worksheet1):
            yield row

还要注意两件事:

  1. 添加yield from / yield会使get_rows函数成为生成函数,从而更改semantics of the return iter_rows(worksheet1) line。您现在想要yield from两个分支。

  2. 当您拥有“csv”时,您最初编写get_rows的方式不会返回生成器。 csv.reader对象不是生成器(也不是,我相信worksheet.iter_rows个对象,但我不知道,因为我不使用openpyxl)。 “xlsx”分支返回生成器的原因是因为您明确地将调用返回到iter_rows,您已将其定义为生成器。您的“csv”分支返回csv.reader个对象。后者是一个懒惰的可迭代,但它不是一个生成器。前一个生成器。并非所有的迭代都是生成器,但是生成器被添加为语言构造以便于编写可迭代,但现在已经扩展为能够执行各种奇特的东西,例如协同程序。请参阅this answer一个着名的问题,我认为这个问题比接受的答案要好。

答案 1 :(得分:2)

问题与您处理文件的方式有关。函数中的两个分支都返回一个迭代器,但CSV分支使用with语句在return时自动关闭文件。这意味着你获得的迭代器csv.reader是无用的,因为它尝试读取的文件在顶级代码可以使用它时已经关闭。

解决此问题的一种方法是使get_rows函数成为生成器。如果您yield from csv.reader而不是return,则在完全读取文件(或丢弃生成器)之前,文件不会关闭。您还需要yield from为其他分支编写的iter_rows生成器。