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

fma*_*ark 396 python language-features with-statement

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

  1. 什么是with设计用于的Python 语句?
  2. 你用它来做什么?
  3. 是否有任何我需要注意的问题,或与其使用相关的常见反模式?任何try..finally比它更好用的情况with
  4. 为什么它的使用范围更广?
  5. 哪些标准库类与它兼容?

Tam*_*más 378

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

  2. 一些示例是:使用打开文件,使用with open(filename) as fp:获取锁with lock:(其中lock是实例threading.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
    
    Run Code Online (Sandbox Code Playgroud)

    这是临时重定向的另一个示例sys.stdin,sys.stdout以及sys.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"
    
    Run Code Online (Sandbox Code Playgroud)

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

    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
    
    Run Code Online (Sandbox Code Playgroud)

  • 感谢您将比较添加到RAII.作为一名C++程序员,我告诉了我需要知道的一切. (17认同)
  • @Musixauce3000 - 使用`with`并不需要"读取"实际文件.`with`调用`open` - 它不知道你需要用它做什么 - 你可能想要搜索一下. (3认同)

sys*_*out 86

我建议两个有趣的讲座:

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

2. 你可以这样做:

with open("foo.txt") as foo_file:
    data = foo_file.read()
Run Code Online (Sandbox Code Playgroud)

要么

from contextlib import nested
with nested(A(), B(), C()) as (X, Y, Z):
   do_something()
Run Code Online (Sandbox Code Playgroud)

或(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))
Run Code Online (Sandbox Code Playgroud)

要么

lock = threading.Lock()
with lock:
    # Critical section of code
Run Code Online (Sandbox Code Playgroud)

3. 我在这里看不到任何反模式.
引用潜入Python:

试试......最终是好的.与更好.

4. 我想这是关系到程序员的使用习惯try..catch..finally的语句从其他语言.

  • 当你处理线程化同步对象时,它真正发挥作用.在Python中比较少见,但是当你需要它们时,你真的需要`with`. (3认同)

Ten*_*she 38

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

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

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

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


Joh*_*ooy 26

反模式的一个例子可能是使用with一个循环内时,它会更有效率有with外循环

例如

for row in lines:
    with open("outfile","a") as f:
        f.write(row)
Run Code Online (Sandbox Code Playgroud)

VS

with open("outfile","a") as f:
    for row in lines:
        f.write(row)
Run Code Online (Sandbox Code Playgroud)

第一种方法是打开和关闭每个row可能导致性能问题的文件,与打开和关闭文件的第二种方式相比.


Jud*_*ill 26

再次为了完整性,我将为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
Run Code Online (Sandbox Code Playgroud)

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


ste*_*anB 10

参见PEP 343 - 'with'语句,最后有一个示例部分.

...新的语句"with"到Python语言,可以分解try/finally语句的标准用法.


cob*_*bal 5

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

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