python“ with”语句的目的是什么?


418

with今天是第一次遇到Python 语句。我已经使用Python几个月了,甚至不知道它的存在!考虑到它的地位有些晦涩,我认为值得一问:

  1. Python with语句旨在用于什么?
  2. 你用它来做什么?
  3. 我需要了解任何陷阱,还是与其使用相关的常见反模式?有什么try..finally比这更好用的情况with吗?
  4. 为什么没有更广泛地使用它?
  5. 哪些标准库类与之兼容?

5
仅作记录,这里是with Python 3文档。
阿列克谢

从Java背景的,它可以帮助我记住它的相应的“尝试 Java中的资源”,即使这可能不完全正确的。
vefthym '19

Answers:


399
  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
    

20
感谢您将比较添加到RAII。作为一名C ++程序员,他告诉我了我需要知道的一切。
Fred Thomsen 2014年

好吧,让我澄清一下。您是说该with语句旨在用数据填充变量,直到其下的指令完成为止,然后释放该变量?
Musixauce3000 '16

因为我用它来打开py脚本。with open('myScript.py', 'r') as f: pass。我希望能够调用该变量f以查看文档的文本内容,因为如果f通过常规open语句将文档分配给该变量,则会出现此情况f = open('myScript.py').read()。但是我得到了以下内容:<_io.TextIOWrapper name='myScript.py' mode='r' encoding='cp1252'>。这是什么意思?
Musixauce3000 '16

3
@ Musixauce3000-使用with不会删除read对实际文件的需要。该with电话open-它不知道你需要用它做什么-你可能想要做一个寻求实例。
Tony Suffolk

@ Musixauce3000 with语句可以用数据填充变量或对环境进行其他更改,直到完成其下的指令,然后进行所需的任何清理。可以执行的清除工作包括关闭打开的文件之类的操作,或者如本例中@Tamas所述,将目录更改回以前的位置,等等。由于Python具有垃圾回收功能,因此释放变量并不重要用例。 with通常用于其他类型的清理。
Bob Steinke

89

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

  • PEP 343 “ with”声明
  • 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进入Python

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

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


4
当您处理线程同步对象时,它确实可以发挥作用。在Python中相对较少,但是当您需要它们时,您确实需要with
熟练地

1
diveintopython.org已关闭(永久?)。
反映

一个很好的答案示例,打开文件是一个很好的示例,它显示了打开,隐藏,关闭文件的操作,并使用自定义的引用名称彻底隐藏了文件操作
Angry 84

40

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

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

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

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


27

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

我在“超几何测试”中经常使用此功能,该测试需要将大量数字除以形式阶乘。在进行基因组比例计算时,必须注意舍入和溢出错误。


26

反模式的一个例子可能是使用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而第二种方法是一次打开和关闭文件,这可能会导致性能问题。



5

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

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



3

开箱即用支持的另一个示例是流行的数据库模块的对象,当您习惯于使用内置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对象psycopg2也是上下文管理器。从有关行为的相关文档中:

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


3

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

的一般形式:

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.