是否可以内联使用 with 语句?

Is it possible to use with statements inline?

例如,这里是非内联代码:

with x:
    do(stuff)

我可以像这样在一行中完成:

with x: do(stuff)

但是我想知道是否有办法做这样的事情(内联):

a = [(lambda x: with x: do(stuff))(x) for x in someList]

编辑:

详细来说,假设我有一个对象列表,每个对象都有自己的上下文。这意味着 do(stuff) 在使用 with x 时会有不同的行为,上面的语句将遍历该列表中的每个对象,输入它们的上下文,运行 do(stuff),然后继续下一个。

由于我已经尝试过这样做,但它不起作用,我想知道是否有任何方法可以做到这一点,以及如何做到这一点。

上面代码的扩展版本看起来像这样(并且有效):

a = []
for x in someList:
    with x: a.append( do(stuff) )

好吧,既然你能接受解决方法,那么 more-itertools.with_iter 可能是你最好的选择。

注意它是第 3 方软件包,因此如果您还没有安装它,则需要安装它

from more_itertools import with_iter

a = [do(stuff) for _ in with_iter(x)]
#your example lacks what do and x really are/do so this comprehension is inferred

如果您不想使用第 3 方包,那么 with_iter 所做的就是:

def with_iter(context_manager):
    with context_manager as iterable:
        yield from iterable

所以这很容易自己实现。

特别是对于 lambda 表达式,这是不可能的(处理 lambda 的部分原因是您不能在其中分配变量)。一个解决方法是提前构造一个合适的函数,然后让一个 lambda 调用那个合适的函数:

def _f(x):
    with x as y: 
       return do(stuff)

a = [_f(x) for x in someList]

我无法让 with: 在 lambda 中工作,不幸的是你也不能手动执行 open/close 例程 - 我怀疑出于同样的原因(你不能分配lambda 中的变量)。


但是,使用 lambdas 和 python 3.8 中引入的赋值运算符,那么你可以这样做:

>>> a = [(lambda x:(f := open(x), f.read(), f.close())[1])(i) 
         for i in ('hello.txt', 'dunno.txt', 'goodbye.txt')]
>>> print(a)
['Hello', 'I dunno', 'Goodbye']

它使用元组作为将多个语句放入 lambda 的模拟。简而言之,它按顺序执行以下操作:

  1. 在元组的第一个元素中,使用赋值运算符创建命名变量
  2. 在元组的第二个元素中,对新创建的 return 有值的命名变量执行操作。将此值存储在元组的第二个元素中。
  3. 在元组的第三个元素中,再次使用命名变量关闭我们最初在元组的第一部分打开的资源。在这种情况下,file.close() returns None,我们不关心,因为它实际上不会引发错误。
  4. 告诉 lambda return 元组的第二个元素(我们在其中对打开的资源执行重要操作的元素)。

这利用了 python 3.8 中新添加的赋值运算符,以及元组按从前到后的顺序求值的事实。理论上,您可以使用此范例在元组内实现整个函数,尽管该代码会变得非常复杂。这是 unpythonic 原样。

免责声明:此方法确实需要手动打开和关闭文件(或者,在你的情况下,我猜,手动调用 __enter__()__exit__() ),因此它没有完全封装 with 的功能。我不知道你会如何对此进行错误处理。

我使用 python 3.8 测试版对此进行了测试,3.8 直到 at least October 21 2019 才会正确发布。这在 3.8 之前的任何当前存在的 python 版本中都不起作用。我不知道这是否符合预期的功能,或者它是否是一个错误,所以如果您出于任何原因选择尝试使用它,请小心。