理解使用 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
,而不是依赖于文件系统中的文件。
我有一个场景,当我使用生成器理解实现解决方案时与我使用 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
,而不是依赖于文件系统中的文件。