是否可以内联使用 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 的模拟。简而言之,它按顺序执行以下操作:
- 在元组的第一个元素中,使用赋值运算符创建命名变量
- 在元组的第二个元素中,对新创建的 return 有值的命名变量执行操作。将此值存储在元组的第二个元素中。
- 在元组的第三个元素中,再次使用命名变量关闭我们最初在元组的第一部分打开的资源。在这种情况下,
file.close()
returns None
,我们不关心,因为它实际上不会引发错误。
- 告诉 lambda return 元组的第二个元素(我们在其中对打开的资源执行重要操作的元素)。
这利用了 python 3.8 中新添加的赋值运算符,以及元组按从前到后的顺序求值的事实。理论上,您可以使用此范例在元组内实现整个函数,尽管该代码会变得非常复杂。这是 unpythonic 原样。
免责声明:此方法确实需要手动打开和关闭文件(或者,在你的情况下,我猜,手动调用 __enter__()
和 __exit__()
),因此它没有完全封装 with
的功能。我不知道你会如何对此进行错误处理。
我使用 python 3.8 测试版对此进行了测试,3.8 直到 at least October 21 2019 才会正确发布。这在 3.8 之前的任何当前存在的 python 版本中都不起作用。我不知道这是否符合预期的功能,或者它是否是一个错误,所以如果您出于任何原因选择尝试使用它,请小心。
例如,这里是非内联代码:
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 的模拟。简而言之,它按顺序执行以下操作:
- 在元组的第一个元素中,使用赋值运算符创建命名变量
- 在元组的第二个元素中,对新创建的 return 有值的命名变量执行操作。将此值存储在元组的第二个元素中。
- 在元组的第三个元素中,再次使用命名变量关闭我们最初在元组的第一部分打开的资源。在这种情况下,
file.close()
returnsNone
,我们不关心,因为它实际上不会引发错误。 - 告诉 lambda return 元组的第二个元素(我们在其中对打开的资源执行重要操作的元素)。
这利用了 python 3.8 中新添加的赋值运算符,以及元组按从前到后的顺序求值的事实。理论上,您可以使用此范例在元组内实现整个函数,尽管该代码会变得非常复杂。这是 unpythonic 原样。
免责声明:此方法确实需要手动打开和关闭文件(或者,在你的情况下,我猜,手动调用 __enter__()
和 __exit__()
),因此它没有完全封装 with
的功能。我不知道你会如何对此进行错误处理。
我使用 python 3.8 测试版对此进行了测试,3.8 直到 at least October 21 2019 才会正确发布。这在 3.8 之前的任何当前存在的 python 版本中都不起作用。我不知道这是否符合预期的功能,或者它是否是一个错误,所以如果您出于任何原因选择尝试使用它,请小心。