我今天第一次了解到 Python 的 with
语句。虽然我已经轻松使用 Python 几个月了,但甚至都不知道它的存在!鉴于其有点模糊的地位,我认为值得问一下:
with
语句设计用于什么?- 你用它来做什么?
- 有任何需要注意的问题或常见反模式与其使用相关的问题吗?在哪些情况下最好使用
try..finally
而不是with
? - 为什么它没有被更广泛地使用?
- 哪些标准库类与其兼容?
我今天第一次了解到 Python 的 with
语句。虽然我已经轻松使用 Python 几个月了,但甚至都不知道它的存在!鉴于其有点模糊的地位,我认为值得问一下:
with
语句设计用于什么?try..finally
而不是 with
?我相信其他用户之前已经回答过这个问题,因此我只是为了完整性而添加这个回答:使用with
语句可以通过封装通用的准备和清理任务在所谓的上下文管理器中简化异常处理。更多细节可以在PEP 343中找到。例如,open
语句本身就是一个上下文管理器,它允许您打开一个文件,在使用with
语句的上下文中保持该文件处于打开状态,并在您离开上下文时立即关闭它,无论您是由于异常还是在正常控制流期间离开的。因此,with
语句可以以类似于C++中的RAII模式的方式使用:一些资源被with
语句获取并在您离开with
上下文时被释放。
一些例子包括使用with open(filename) as fp:
来打开文件,使用with lock:
(其中lock
是threading.Lock
的实例)来获取锁。您还可以使用contextlib
中的contextmanager
装饰器构建自己的上下文管理器。例如,当我需要临时更改当前目录并返回原来的目录时,我经常使用它:
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.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"
最后,又有一个例子创建了一个临时文件夹,并在离开上下文时清理它:
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
with
语句的设计是将变量填充数据,直到其下方的指令完成,然后释放该变量? - Musixauce3000with open('myScript.py', 'r') as f: pass
。我期望能够调用变量f
来查看文档的文本内容,因为如果文档通过常规的open
语句分配给f
,那么这就是所显示的内容:f = open('myScript.py').read()
。但是我得到了以下结果:<_io.TextIOWrapper name='myScript.py' mode='r' encoding='cp1252'>
。这是什么意思? - Musixauce3000with
并不能代替操作文件的必要性。with
只是调用open
函数,它不知道你需要对文件进行何种操作——例如,你可能需要执行搜索操作。 - Tony Suffolk 66with
语句可以将变量填充数据或对环境进行其他更改,直到其下方的指令完成,然后执行任何所需的清理。可以进行的清理类型包括关闭打开的文件,或者像@Tamas在此示例中那样将目录更改回之前的位置等。由于Python具有垃圾收集功能,释放变量不是重要的用例。with
通常用于其他类型的清理。 - Bob Steinke我建议两个有趣的讲座:
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. 我没有看到任何反模式。
引用Python深入浅出的话:
try..finally是好的,但with更好。
4. 我猜这可能与程序员习惯于从其他语言使用try..catch..finally
语句有关。
with
。 - detly为了完整起见,我将添加我最有用的使用情况来说明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
我经常在超几何测试中使用这个方法,需要计算阶乘产生的大数的除法。当你进行基因组规模的计算时,必须小心四舍五入和溢出错误。
一个反面模式的例子可能是在循环内使用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
打开和关闭文件,这可能会导致性能问题,相比之下第二种方法只需打开和关闭文件一次。
请查看PEP 343——'with'语句,其中在末尾有一个示例部分。
...引入了新语句“with”到Python语言中,以便将try/finally语句的标准使用因素分离出来。
已经比较详细地涵盖了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”语句通过提供清理活动使异常处理更简单。
“with”的一般形式:
with open(“file name”, “mode”) as file_var:
processing statements
注意: 在调用 file_var.close()
后不需要再调用 close()
方法来关闭文件。
open()
行为方式的人来说,这可能有点令人困惑,就是流行的数据库模块中的 connection
对象,例如:
connection
对象是上下文管理器,因此可以在 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()
psycopg2
的 cursor
对象也是上下文管理器。从相关行为文档中可以看出:
当
cursor
退出with-block
时,它会关闭并释放任何与其关联的资源。交易状态不受影响。
with
的部分。 - Alexey