问题:“ with”语句中有多个变量?

使用withPython中的语句可以声明多个变量吗?

就像是:

from __future__ import with_statement

with open("out.txt","wt"), open("in.txt") as file_out, file_in:
    for line in file_in:
        file_out.write(line)

…还是同时清理两个资源是问题吗?

Is it possible to declare more than one variable using a with statement in Python?

Something like:

from __future__ import with_statement

with open("out.txt","wt"), open("in.txt") as file_out, file_in:
    for line in file_in:
        file_out.write(line)

… or is cleaning up two resources at the same time the problem?


回答 0

从v3.1Python 2.7 开始,Python 3中是可能的。新with语法支持多个上下文管理器:

with A() as a, B() as b, C() as c:
    doSomething(a,b,c)

不同于contextlib.nested,这保证了即使或方法引发异常,ab__exit__()调用C()__enter__()

您也可以在较新的定义中使用较早的变量(以下为h / t Ahmad):

with A() as a, B(a) as b, C(a, b) as c:
    doSomething(a, c)

It is possible in Python 3 since v3.1 and Python 2.7. The new with syntax supports multiple context managers:

with A() as a, B() as b, C() as c:
    doSomething(a,b,c)

Unlike the contextlib.nested, this guarantees that a and b will have their __exit__()‘s called even if C() or it’s __enter__() method raises an exception.

You can also use earlier variables in later definitions (h/t Ahmad below):

with A() as a, B(a) as b, C(a, b) as c:
    doSomething(a, c)

回答 1

contextlib.nested 支持这一点:

import contextlib

with contextlib.nested(open("out.txt","wt"), open("in.txt")) as (file_out, file_in):

   ...

更新:
要引用有关contextlib.nested以下内容的文档:

从2.7版开始不推荐使用:with语句现在直接支持此功能(不存在容易出错的易错问题)。

有关更多信息,请参见RafałDowgird的答案

contextlib.nested supports this:

import contextlib

with contextlib.nested(open("out.txt","wt"), open("in.txt")) as (file_out, file_in):

   ...

Update:
To quote the documentation, regarding contextlib.nested:

Deprecated since version 2.7: The with-statement now supports this functionality directly (without the confusing error prone quirks).

See Rafał Dowgird’s answer for more information.


回答 2

请注意,如果将变量分成几行,则必须使用反斜杠来包装换行符。

with A() as a, \
     B() as b, \
     C() as c:
    doSomething(a,b,c)

括号无效,因为Python会创建一个元组。

with (A(),
      B(),
      C()):
    doSomething(a,b,c)

由于元组缺少__enter__属性,因此会出现错误(描述性不够,并且无法标识类类型):

AttributeError: __enter__

如果尝试as在括号内使用,Python会在解析时捕获错误:

with (A() as a,
      B() as b,
      C() as c):
    doSomething(a,b,c)

SyntaxError:语法无效

https://bugs.python.org/issue12782似乎与此问题有关。

Note that if you split the variables into lines, you must use backslashes to wrap the newlines.

with A() as a, \
     B() as b, \
     C() as c:
    doSomething(a,b,c)

Parentheses don’t work, since Python creates a tuple instead.

with (A(),
      B(),
      C()):
    doSomething(a,b,c)

Since tuples lack a __enter__ attribute, you get an error (undescriptive and does not identify class type):

AttributeError: __enter__

If you try to use as within parentheses, Python catches the mistake at parse time:

with (A() as a,
      B() as b,
      C() as c):
    doSomething(a,b,c)

SyntaxError: invalid syntax

https://bugs.python.org/issue12782 seems to be related to this issue.


回答 3

我认为您想这样做:

from __future__ import with_statement

with open("out.txt","wt") as file_out:
    with open("in.txt") as file_in:
        for line in file_in:
            file_out.write(line)

I think you want to do this instead:

from __future__ import with_statement

with open("out.txt","wt") as file_out:
    with open("in.txt") as file_in:
        for line in file_in:
            file_out.write(line)

回答 4

因为Python 3.3,你可以使用类ExitStackcontextlib模块。

它可以管理动态数量的上下文感知对象,这意味着如果您不知道要处理多少文件,它将证明特别有用。

文档中提到的规范用例正在管理动态文件数量。

with ExitStack() as stack:
    files = [stack.enter_context(open(fname)) for fname in filenames]
    # All opened files will automatically be closed at the end of
    # the with statement, even if attempts to open files later
    # in the list raise an exception

这是一个通用示例:

from contextlib import ExitStack

class X:
    num = 1

    def __init__(self):
        self.num = X.num
        X.num += 1

    def __repr__(self):
        cls = type(self)
        return '{cls.__name__}{self.num}'.format(cls=cls, self=self)

    def __enter__(self):
        print('enter {!r}'.format(self))
        return self.num

    def __exit__(self, exc_type, exc_value, traceback):
        print('exit {!r}'.format(self))
        return True

xs = [X() for _ in range(3)]

with ExitStack() as stack:
    print(stack._exit_callbacks)
    nums = [stack.enter_context(x) for x in xs]
    print(stack._exit_callbacks)
print(stack._exit_callbacks)
print(nums)

输出:

deque([])
enter X1
enter X2
enter X3
deque([<function ExitStack._push_cm_exit.<locals>._exit_wrapper at 0x7f5c95f86158>, <function ExitStack._push_cm_exit.<locals>._exit_wrapper at 0x7f5c95f861e0>, <function ExitStack._push_cm_exit.<locals>._exit_wrapper at 0x7f5c95f86268>])
exit X3
exit X2
exit X1
deque([])
[1, 2, 3]

Since Python 3.3, you can use the class ExitStack from the contextlib module.

It can manage a dynamic number of context-aware objects, which means that it will prove especially useful if you don’t know how many files you are going to handle.

The canonical use-case that is mentioned in the documentation is managing a dynamic number of files.

with ExitStack() as stack:
    files = [stack.enter_context(open(fname)) for fname in filenames]
    # All opened files will automatically be closed at the end of
    # the with statement, even if attempts to open files later
    # in the list raise an exception

Here is a generic example:

from contextlib import ExitStack

class X:
    num = 1

    def __init__(self):
        self.num = X.num
        X.num += 1

    def __repr__(self):
        cls = type(self)
        return '{cls.__name__}{self.num}'.format(cls=cls, self=self)

    def __enter__(self):
        print('enter {!r}'.format(self))
        return self.num

    def __exit__(self, exc_type, exc_value, traceback):
        print('exit {!r}'.format(self))
        return True

xs = [X() for _ in range(3)]

with ExitStack() as stack:
    print(stack._exit_callbacks)
    nums = [stack.enter_context(x) for x in xs]
    print(stack._exit_callbacks)
print(stack._exit_callbacks)
print(nums)

Output:

deque([])
enter X1
enter X2
enter X3
deque([<function ExitStack._push_cm_exit.<locals>._exit_wrapper at 0x7f5c95f86158>, <function ExitStack._push_cm_exit.<locals>._exit_wrapper at 0x7f5c95f861e0>, <function ExitStack._push_cm_exit.<locals>._exit_wrapper at 0x7f5c95f86268>])
exit X3
exit X2
exit X1
deque([])
[1, 2, 3]

回答 5

在Python 3.1+中,您可以指定多个上下文表达式,它们将像with嵌套了多个语句一样进行处理:

with A() as a, B() as b:
    suite

相当于

with A() as a:
    with B() as b:
        suite

这也意味着您可以在第二个表达式中使用第一个表达式的别名(在使用数据库连接/游标时很有用):

with get_conn() as conn, conn.cursor() as cursor:
    cursor.execute(sql)

In Python 3.1+ you can specify multiple context expressions, and they will be processed as if multiple with statements were nested:

with A() as a, B() as b:
    suite

is equivalent to

with A() as a:
    with B() as b:
        suite

This also means that you can use the alias from the first expression in the second (useful when working with db connections/cursors):

with get_conn() as conn, conn.cursor() as cursor:
    cursor.execute(sql)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。