如何使上下文管理器在未完全消耗生成器时成功返回生成器退出?

问题描述 投票:0回答:1

我正在使用 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
,因为重构会花费太长时间。

python generator contextmanager
1个回答
0
投票

为了确保打印

'__exit__'
,请将
open_file
更改为:

@contextlib.contextmanager
def open_file():
    print('__enter__')
    try:
        yield (f'line {i}' for i in (1, 2, 3))
    finally:
        print('__exit__')
© www.soinside.com 2019 - 2024. All rights reserved.