理解使用 yield from generator comprehension 之间的一些差异

Understanding some differences between using yield from generator comprehension

我有一个场景,当我使用生成器理解实现解决方案时与我使用 yield 关键字时表现不同。

下面是两个例子:

示例 A(可行):

def get_latest_products(self) -> Generator:
    first = True  # avoid appending the CSV header (first row)
    with open('path/to/my/file.csv')as file:
        file = get_csv_reader(file)
        for row in file:
            if not first:
                product = PageProduct(
                    page_name=row[0],
                    category_id=row[1],
                    product_id=row[2],
                    product_url=row[3],
                    product_name=row[4],
                    product_price=row[5],
                )
                yield product
            first = False

示例 B(更优雅,如果没有 I/O 处理也可以工作):

def get_latest_products(self) -> Generator:
    with open('path/to/my/file.csv') as file:
        file = get_csv_reader(file)
        return (
            PageProduct(
                page_name=row[0],
                category_id=row[1],
                product_id=row[2],
                product_url=row[3],
                product_name=row[4],
                product_price=row[5],
            ) for index, row in enumerate(file) if index > 0
        )

当实现示例 B 时,我认为它更具可读性和优雅性,我得到:(当我调用 next()

  File "/Users/xxx/xxx/collect_products.py", line 157, in <genexpr>
    return (
ValueError: I/O operation on closed file.

虽然示例 A 实现工作正常。为什么?

您需要在 with 语句的上下文中定义 并使用 生成器。执行此操作的最佳方法是让您的方法将可迭代对象(可能是文件句柄,也可能是其他东西)作为参数,而不是打开文件本身。

from itertools import islice


def get_latest_products(self, fh) -> Generator:
    f = get_csv_reader(fh)
    yield from (PageProduct(...) for row in islice(fh, 1, None))

with 语句中调用方法:

with open('path/to/my/file.csv', r) as f:
    for product in foo.get_latest_products(f):
        ...

这也使测试更容易,因为您可以使用任何可迭代对象调用 get_latest_products,而不是依赖于文件系统中的文件。