481

我今天第一次遇到 Pythonwith语句。几个月来我一直在轻松使用 Python,甚至不知道它的存在!鉴于其有点模糊的地位,我认为值得一问:

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

11 回答 11

434
  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
    

    这是另一个临时重定向的示例sys.stdinsys.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"
    

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

    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
    
于 2010-06-10T08:51:37.500 回答
99

我会推荐两个有趣的讲座:

  • PEP 343 “与”声明
  • Effbot理解 Python 的“with”语句

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

2. 你可以这样做:

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

或者

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

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

或者

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

3. 我在这里没有看到任何反模式。
引用Dive into Python

试试……终于好了。有更好的。

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

于 2010-06-10T07:49:10.790 回答
44

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

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

Python 库中的许多资源都遵循with语句所需的协议,因此可以开箱即用地使用。但是,任何人都可以通过实现有据可查的协议来制作可在 with 语句中使用的资源:PEP 0343

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

于 2010-06-10T09:53:05.713 回答
28

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

例如

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

对比

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

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

于 2010-06-10T10:25:19.500 回答
28

再次为了完整起见,我将添加我最有用的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

我在超几何测试中经常使用它,这需要对大量数进行除法,从而产生形状因子。当您进行基因组规模计算时,您必须小心舍入和溢出错误。

于 2010-06-10T17:22:32.117 回答
10

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

... Python 语言中的新语句“with”,可以将 try/finally 语句的标准用法分解出来。

于 2010-06-10T07:45:21.527 回答
5

第 1 点、第 2 点和第 3 点得到了很好的覆盖:

4:比较新,只有python2.6+(或者python2.5 using from __future__ import with_statement)才有

于 2010-06-10T08:34:42.703 回答
4

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

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

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

于 2010-06-10T07:45:15.537 回答
3

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

这些connection对象是上下文管理器,因此可以在 a 中开箱即with-statement用地使用,但是在使用上述内容时请注意:

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

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

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对象psycopg2也是上下文管理器。从有关行为的相关文档中:

当 acursor退出时,with-block它会关闭,释放最终与它关联的任何资源。事务的状态不受影响。

于 2017-08-28T21:28:06.717 回答
3

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

with的一般形式:

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

注意:无需通过调用关闭close()文件file_var.close()

于 2018-02-13T16:15:06.433 回答
1

这里的答案很棒,但只是添加一个对我有帮助的简单答案:

  • open返回一个file
  • 从 2.6开始,python 添加了方法__enter__和.__exit__file
  • with就像一个 for 循环调用__enter__,运行一次循环然后调用__exit__
  • with适用于任何具有__enter____exit__

文件被锁定并且在关闭之前不能被其他进程重用,__exit__关闭它。

来源:http ://web.archive.org/web/20180310054708/http://effbot.org/zone/python-with-statement.htm

于 2022-01-03T11:32:00.027 回答