我正在使用 Python 中的
with
块读取 CSV 文件。这模拟了正在打开的文件 (__enter__
)、按需生成的文件行 (yield (...)
) 以及正在关闭的文件 (__exit__
)。
@contextlib.contextmanager
def open_file():
print('__enter__')
yield (f'line {i}' for i in (1, 2, 3))
print('__exit__')
我想读取此 CSV 的行并希望为每一行返回一个模型。所以我正在做类似的事情:
class Model:
def __init__(self, value: int):
self.value = value
def create_models():
with open_file() as f:
for line in f:
yield Model(int(line.split(' ')[-1]))
如果我运行整个文件,它将调用
__enter__
和 __exit__
回调:
def main1():
for model in create_models():
print(model.value)
main1()
# __enter__
# 1
# 2
# 3
# __exit__
但是,如果我不需要文件中的所有模型,它不会调用
__exit__
回调:
def main2():
for model in create_models():
print(model.value)
if model.value % 2 == 0:
break
main2()
# __enter__
# 1
# 2
如何实现即使我不迭代整个子生成器也会退出的上下文管理器?还可能吗?
请注意,在这个示例中,我无法更改
open_file
上下文管理器,因为在我的代码中,它是来自 Python 的 open
内置(我在这里使用 open_file
只是为了最小化且可重现)示例)。
我想做的是类似于 Dart 中的
Stream
:
import 'dart:convert';
class Model {
final int value;
const Model(this.value);
}
void main() {
File('file.csv')
.openRead()
.transform(utf8.decoder)
.transform(const LineSplitter())
.map((line) => int.parse(line.split(',')[1]))
.map((value) => Model(value))
.takeWhile((model) => model.value % 2 == 0)
.forEach((model) {
print(model.value);
});
}
这将成功关闭文件,即使我没有消耗其中的所有行(
takeWhile
)。由于我的Python代码库都是同步代码,所以我想避免引入async
和await
,因为重构会花费太长时间。
为了确保打印
'__exit__'
,请将 open_file
更改为:
@contextlib.contextmanager
def open_file():
print('__enter__')
try:
yield (f'line {i}' for i in (1, 2, 3))
finally:
print('__exit__')