什么是python“with”语句专为什么设计的?

时间:2010-06-10 07:35:22

标签: python language-features with-statement

我今天第一次看到了Python with声明。我已经使用Python几个月了,甚至不知道它的存在!鉴于其地位有点模糊,我认为值得问:

  1. 什么是Python with语句 旨在用于?
  2. 做什么 你用它吗?
  3. 有没有 我需要注意的问题,或者 与...相关的常见反模式 它的用途?是否最好使用try..finally而不是with
  4. 为什么不能更广泛地使用它?
  5. 哪些标准库类与之兼容?

11 个答案:

答案 0 :(得分:361)

  1. 我相信在我之前已经有其他用户回答了这个问题,所以我只是为了完整性而添加它:with语句通过封装常见的准备和清理任务来简化异常处理。叫context managers。更多细节可以在PEP 343中找到。例如,open语句本身就是一个上下文管理器,它允许您打开一个文件,只要执行位于您使用它的with语句的上下文中,就保持打开状态,并且离开上下文后立即关闭它,无论您是因为异常还是在常规控制流程中离开它。因此,with语句可以与C ++中的RAII pattern类似的方式使用:某些资源由with语句获取,并在您离开with上下文时释放。 / p>

  2. 一些示例是:使用with open(filename) as fp:打开文件,使用with lock:获取锁定(其中lockthreading.Lock的实例)。您还可以使用contextmanager中的contextlib装饰器构建自己的上下文管理器。例如,当我必须暂时更改当前目录然后返回到我所在的位置时,我经常使用它:

    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重定向到其他文件句柄并稍后恢复的示例:

    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"
    

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

    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
    

答案 1 :(得分:85)

我建议两个有趣的讲座:

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

<强> 2 你可以这样做:

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

OR

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

OR(Python 3.1)

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

OR

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

第3  我在这里看不到任何反模式 引用Dive into Python

  

尝试......最终是好的。更好。

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

答案 2 :(得分:35)

Python with语句是C ++中常用的Resource Acquisition Is Initialization习语的内置语言支持。它旨在允许安全获取和释放操作系统资源。

with语句在范围/块中创建资源。您使用块中的资源编写代码。当块退出时,无论块中代码的结果如何(即块是正常退出还是由于异常),都会干净地释放资源。

Python库中的许多资源遵循with语句所要求的协议,因此可以与它一起使用。但是,任何人都可以通过实施记录良好的协议来创建可以在with语句中使用的资源:PEP 0343

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

答案 3 :(得分:25)

反模式的一个示例可能是在循环中使用with,而在循环外使with更有效

例如

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

VS

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

第一种方法是打开和关闭每个row的文件,这可能会导致性能问题,而第二种方式是打开并关闭文件一次。

答案 4 :(得分:24)

再次为了完整性,我将为with语句添加我最有用的用例。

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

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

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

我在Hypergeometric Test中经常使用它,需要对大数字进行除法,从而得到形式因子。进行基因组规模计算时,必须注意舍入和溢出错误。

答案 5 :(得分:9)

请参阅PEP 343 - The 'with' statement,最后有一个示例部分。

  

...对Python的新声明“with”   语言       可以分解try / finally语句的标准用法。

答案 6 :(得分:4)

第1,2和3点得到了相当好的覆盖:

4:它相对较新,仅适用于python2.6 +(或使用from __future__ import with_statement的python2.5)

答案 7 :(得分:3)

with语句适用于所谓的上下文管理器:

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

这个想法是在离开'with'块后通过进行必要的清理来简化异常处理。一些python内置函数已经作为上下文管理器工作。

答案 8 :(得分:2)

开箱即用支持的另一个例子,当你习惯了内置open()行为的方式时,可能有点令人困惑的一个例子是connection个对象流行的数据库模块,如:

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

  

with-block完成后,无论是否有例外,连接未关闭。如果with-block以异常结束,则回滚事务,否则提交事务。

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

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()

在上面的示例中,您将注意到cursor的{​​{1}}个对象也是上下文管理器。从有关行为的相关文档:

  

psycopg2退出cursor时,它会关闭,释放最终与之关联的任何资源。交易状态不受影响。

答案 9 :(得分:2)

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

一般形式:

with open(“file name”, “mode”) as file-var:
    processing statements

注意:无需通过在file-var.close()上调用close()来关闭文件

答案 10 :(得分:0)

与@JudoWill 提到的用法类似,它可用于提供在块结束时自动销毁的局部变量,而无需显式删除资源。

示例:

function1(<complex and deep expression>)
function2(<same complex and deep expression>)

可以改写为:

expr = <complex and deep expression>
function1(expr)
function1(expr)
expr = None   # to delete all resources, or wait till end of function

或作为:

with <complex and deep expression> as expr:
  function1(expr)
  function1(expr)

# expr is out of scope, hence any resources are deleted