【问题标题】:Flattening `with` and `try/finally` in Python在 Python 中展平 `with` 和 `try/finally`
【发布时间】:2017-05-30 09:29:39
【问题描述】:

考虑以下代码:

async with app:
    ...
    async with app.resource as rsrc:
        ...
        async with rsrc.foo as bar:
            ...

大量嵌套的withasync with 语句会对代码的可读性产生负面影响,尤其是在测试中,同一个子句可能会被重复使用很多次。

在像 D 这样的语言中,有 scope(exit) ... 构造,它允许您将代码附加到作用域终结器列表中——一旦离开作用域,这段代码就会被执行,让您可以有效地执行 __exit__ 所做的事情,但没有添加缩进。

有没有办法在 Python 中展平 with 并执行类似的操作?

await async_with(app)
scope_exit(app.quit)

...

rsrc = await async_with(app.resource)
scope_exit(rsrc.release)

...

bar = await async_with(rsrc.foo)
scope_exit(lambda: bar.unfrob())

...

或者,有没有办法在退出范围时可靠地执行任意代码?

【问题讨论】:

  • with statement 可以有多个成员。我不确定您是否可以在语句中使用任何结果变量。
  • @Kendas,虽然可以在单个 with 语句中包含多个子句,但如果不诉诸 with(或不存在的 scope_exit),就不可能在两者之间执行任何代码跨度>

标签: python python-3.x


【解决方案1】:

据我所知,你无法实现你想要的。尤其是因为不可能定义这样的“范围”。

即使在你的伪代码中:

await async_with(app)
scope_exit(app.quit)

...

rsrc = await async_with(app.resource)
scope_exit(rsrc.release)

...

bar = await async_with(rsrc.foo)
scope_exit(lambda: bar.unfrob())

raise ExceptionHere()

什么上下文应该[不]捕捉它?

我可能错了,但设置“守卫”以捕获异常的唯一方法是try/except 子句。 with 大致是它的包装。

谈到动机,如果您对嵌套上下文管理器有这么大的问题,您可能应该重构您的核心以将内部上下文提取为函数。您可以查看contextlib 寻求帮助

【讨论】:

  • 如果您在最后引发异常,它应该遵循与嵌套 with 相同的展开流程 - 例如应该调用bar.unfrob(),然后调用rsrc.release(),然后调用app.quit()。如果在scope_exit(lambda: bar.unfrob()) 之前引发了异常,那么就不会有这样的作用域终结器,所以应该只调用rsrc.release()app.quit()
  • 也许我什至应该把它变成一个 PEP 来定义“范围终结器”的概念,以便在 Python 中启用显式 RAII。
  • Tbh,我真的怀疑扁平化这种东西的可能性,但你总是可以尝试:) 我建议阅读以下内容:python.org/dev/peps/pep-0310(实际上,with 的父亲,afaik)python.org/dev/peps/pep-0343with 的直觉)stackoverflow.com/a/5071376/2161778(python 和思想中的 RAII)
【解决方案2】:

我无法忍受 with 声明中的额外缩进...这就是我为压平它们所做的:

嵌套:

def stuff_with_files(path_a, path_b):
    # ... do stuff
    with open(path_a) as fileA:
        # ... do stuff with fileA
        with open(path_b) as fileB:
            # ... do stuff with fileA and fileB

def main():
    stuff_with_files('fileA.txt', 'fileB.txt')

扁平化:

# Extract the 'with' statement.
def with_file(path, func, *args, **kwargs):
    with open(path) as file:
        func(file, *args, **kwargs)


def process_a_b(fileB, results_of_a):
    # ... do stuff with fileB and results_of_a.


def process_a(fileA):
    # ... do stuff with fileA
    results = # ...
    with_file('fileB.txt', process_a_b, results)


def main():
    with_file('fileA.txt', process_a)

【讨论】:

    猜你喜欢
    • 2014-11-27
    • 1970-01-01
    • 2012-01-24
    • 2018-03-19
    • 1970-01-01
    • 2023-03-09
    • 2011-02-20
    • 2021-10-08
    • 1970-01-01
    相关资源
    最近更新 更多