关于语言特性:python”with”语句是为什么而设计的?

What is the python “with” statement designed for?

今天我第一次看到python with声明。我已经轻而易举地使用了几个月,甚至不知道它的存在!考虑到它的地位有些模糊,我想应该问一下:

  • python with语句是什么设计用于?
  • 做什么你用它吗?
  • 有没有明白了,我需要知道,或者常见的反模式与它的用途?有没有比with更好地使用try..finally的情况?
  • 为什么使用得不更广泛?
  • 哪些标准库类与它兼容?

  • 我相信我之前的其他用户已经回答了这个问题,所以我添加它只是为了完整性:with语句通过在所谓的上下文管理器中封装常见的准备和清理任务来简化异常处理。更多详情请参见PEP 343。例如,open语句本身就是一个上下文管理器,它允许您打开一个文件,只要执行在您使用它的with语句的上下文中,就保持它的打开状态,并在您离开上下文时立即关闭它,无论您是因为异常还是在常规控制流中离开它。因此,EDCOX1×0的语句可以以类似于C++中的RAII模式的方式使用:一些资源是通过EDCOX1×0的语句获取的,并且在离开EDCOX1 0上下文时释放。

  • 例如:使用with open(filename) as fp:打开文件,使用with lock:获取锁(其中lockthreading.Lock的实例)。您还可以使用contextlib中的contextmanager修饰器构造自己的上下文管理器。例如,当我必须临时更改当前目录,然后返回到我所在的位置时,我经常使用此选项:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    from contextlib import contextmanager
    import os

    @contextmanager
    def working_directory(path):
        current_dir = os.getcwd()
        os.chdir(path)
        try:
            yield
        finally:
            os.chdir(current_dir)

    with working_directory("data/stuff"):
        # do something within data/stuff
    # here I am back again in the original working directory

    下面是另一个例子,它临时将sys.stdinsys.stdoutsys.stderr重定向到其他文件句柄,并在以后恢复它们:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    from contextlib import contextmanager
    import sys

    @contextmanager
    def redirected(**kwds):
        stream_names = ["stdin","stdout","stderr"]
        old_streams = {}
        try:
            for sname in stream_names:
                stream = kwds.get(sname, None)
                if stream is not None and stream != getattr(sys, sname):
                    old_streams[sname] = getattr(sys, sname)
                    setattr(sys, sname, stream)
            yield
        finally:
            for sname, stream in old_streams.iteritems():
                setattr(sys, sname, stream)

    with redirected(stdout=open("/tmp/log.txt","w")):
         # these print statements will go to /tmp/log.txt
         print"Test entry 1"
         print"Test entry 2"
    # back to the normal stdout
    print"Back to normal stdout again"

    最后,另一个创建临时文件夹并在离开上下文时将其清除的示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    from tempfile import mkdtemp
    from shutil import rmtree

    @contextmanager
    def temporary_dir(*args, **kwds):
        name = mkdtemp(*args, **kwds)
        try:
            yield name
        finally:
            shutil.rmtree(name)

    with temporary_dir() as dirname:
        # do whatever you want

  • 我建议两个有趣的讲座:

    • PEP 343"with"声明
    • effbot了解python的"with"语句

    1。with语句用于用上下文管理器定义的方法包装块的执行。这允许封装通用的try...except...finally使用模式,以方便重用。

    2。你可以这样做:

    1
    2
    with open("foo.txt") as foo_file:
        data = foo_file.read()

    1
    2
    3
    from contextlib import nested
    with nested(A(), B(), C()) as (X, Y, Z):
       do_something()

    或(Python 3.1)

    1
    2
    3
    with open('data') as input_file, open('result', 'w') as output_file:
       for line in input_file:
         output_file.write(parse(line))

    1
    2
    3
    lock = threading.Lock()
    with lock:
        # Critical section of code

    三。我在这里看不到任何反模式。引用Python的话:

    try..finally is good. with is better.

    4。我想这和程序员使用其他语言的try..catch..finally语句的习惯有关。


    Python EDCOX1×0语句是对C++中常用的EDCOX1·20的习惯用语的内置语言支持。它旨在允许安全获取和释放操作系统资源。

    with语句在作用域/块内创建资源。使用块中的资源编写代码。当块退出时,不管块中代码的结果如何(即块是正常退出还是由于异常退出),都将干净地释放资源。

    python库中的许多资源都遵守with语句所需的协议,因此可以在开箱即用的情况下使用它。但是,任何人都可以通过实施文件化的协议(PEP 0343)来获得WITH语句中可以使用的资源。

    每当您获取应用程序中必须明确放弃的资源(如文件、网络连接、锁等)时,都可以使用它。


    为了完整起见,我将为with语句添加最有用的用例。

    我做了很多科学计算,对于某些活动,我需要Decimal库来进行任意精度的计算。我的代码的某些部分需要高精度,而对于大多数其他部分,我需要较少的精度。

    我将默认精度设置为一个较低的数字,然后使用with为某些部分获得更精确的答案:

    1
    2
    3
    4
    5
    6
    from decimal import localcontext

    with localcontext() as ctx:
        ctx.prec = 42   # Perform a high precision calculation
        s = calculate_something()
    s = +s  # Round the final result back to the default precision

    我经常在超几何测试中使用这个方法,它要求将产生阶乘的大数除法。当你做基因组尺度计算时,你必须小心四舍五入和溢出错误。


    反模式的一个例子可能是在循环内部使用with,而在循环外部使用with更有效。

    例如

    1
    2
    3
    for row in lines:
        with open("outfile","a") as f:
            f.write(row)

    VS

    1
    2
    3
    with open("outfile","a") as f:
        for row in lines:
            f.write(row)

    第一种方法是为每个row打开和关闭文件,与第二种方法仅打开和关闭文件一次相比,这可能会导致性能问题。


    参见PEP 343-"with"语句,结尾有一个示例部分。

    ... new statement"with" to the Python
    language to make
    it possible to factor out standard uses of try/finally statements.


    第1点、第2点和第3点被合理地涵盖:

    4:比较新,只在python2.6+中提供(或使用from __future__ import with_statement的python2.5)


    WITH语句与所谓的上下文管理器一起工作:

    http://docs.python.org/release/2.5.2/lib/typecontextmanager.html

    其思想是在离开"with"块后进行必要的清理,从而简化异常处理。一些python内置模块已经作为上下文管理器工作。


    在python中,通常使用"with"语句来打开文件、处理文件中存在的数据,以及在不调用close()方法的情况下关闭文件。"with"语句通过提供清理活动使异常处理更简单。

    一般形式:

    1
    2
    with open("file name","mode") as file-var:
        processing statements

    注意:不需要通过对file-var.close()调用close()来关闭文件。


    另一个现成支持的例子是connection受欢迎的数据库模块的对象,例如:

    • 平方英寸3
    • PycPcG2
    • CXI甲骨文

    connection对象是上下文管理器,因此可以在with-statement中开箱即用,但是在使用上述注意事项时:

    When the with-block is finished, either with an exception or without, the connection is not closed. In case the with-block finishes with an exception, the transaction is rolled back, otherwise the transaction is commited.

    这意味着程序员必须注意自己关闭连接,但允许获取连接,并在多个with-statements中使用,如psycopg2文档所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    conn = psycopg2.connect(DSN)

    with conn:
        with conn.cursor() as curs:
            curs.execute(SQL1)

    with conn:
        with conn.cursor() as curs:
            curs.execute(SQL2)

    conn.close()

    在上面的示例中,您将注意到psycopg2cursor对象也是上下文管理器。从有关行为的文档中:

    When a cursor exits the with-block it is closed, releasing any resource eventually associated with it. The state of the transaction is not affected.