一、with 语句是什么?
当你遇到一些事前先要进行设置,事后要进行清理的情形时,Python的with语句为你提供了一个非常方便的方式。一个非常好的例子就是文件操作,你首先要获得文件句柄,然后通过文件句柄来读取文件内容,最后还要关闭文件。
如果不使用with 语句,你可能会这样来进行文件的操作:
file = open("/tmp/foo.txt")
data = file.read()
file.close()
这里有两个问题。一是你可能会忘记关闭文件;二是出现异常的时候怎么关闭文件。可以通过下面的方式来解决这两个问题。
file=open("/tmp/foo.txt")
try:
data =file.read()
finally:
file.close()
虽然上面的代码能够很好的运行,但是它太冗长了。这时候with语句就有用武之地了。它除了具有简洁的语法外,还能很好的处理异常。上面的代码用with语句来实现是这样的:
with open("/tmp/foo.txt") as file:
data =file.read()
2、with 语句的工作原理是什么?
这看起像是魔法,但是Python的with语句却是比魔法高明多了。基本的思想是with语句后面的表达式所求的值需要有__enter__() 和 __exit__() 方法。
with语句后面的表达式被求值后会得到一个对象,接着会该对象的__enter__()方法会被自动调用;__enter__()方法的返回值会被赋值给跟着as后面的变量。当所有with语句块内的代码被执行后,__exit__()方法会被自动调用。
这个过程可以用下面这个例子来说明:
#!/usr/bin/env python
# with_example01.py
class Sample:
def __enter__(self):
print"In __enter__()"
return"Foo"
def __exit__(self, type, value, trace):
print"In __exit__()"
def get_sample():
return Sample()
with get_sample() as sample:
print"sample:", sample
执行with语句的结果如下:
bash-3.2$ ./with_example01.py
In __enter__()
sample: Foo
In __exit__()
从上面可以知道:
1、__enter__方法被执行了
2、__enter__方法的返回值被赋值给as后面的变量了;在这个例子中是”Foo”赋值给sample
3、执行with语句代码块,打印出sample 值为 Foo
4、__exit__方法被调用
With 语句真正强大的地方在于它能够处理异常。你可能也注意到了,sample的__exit__方法有三个参数:val、type和trace。这些参数在处理异常时非常有用。让我们通过修改上面的代码来看看它是怎么做到的。
#!/usr/bin/env python # with_example02.py class Sample: def __enter__(self): return self def __exit__(self, type, value, trace): print"type:", type print"value:", value print"trace:", trace def do_something(self): bar =1/0 return bar +10 with Sample() as sample: sample.do_something()
注意看这个例子,with语句后面用了Sample()而不是get_sample()。不过这并没有什么影响,只要with语句后面的表达式返回的对象有__enter__() 和 __exit__() 方法就可以了。在这个例子中,Sample的 __enter__()方法返回了新创建的Sample实例,并将其赋值给as后面的sample变量。
代码执行结果如下:
bash-3.2$ ./with_example02.py
type: <type'exceptions.ZeroDivisionError'>
value: integer division or modulo by zero
trace: <traceback object at 0x1004a8128>
Traceback (most recent call last):
File "./with_example02.py", line 19, in <module>
sample.do_something()
File "./with_example02.py", line 15, in do_something
bar= 1/0
ZeroDivisionError: integer division or modulo by zero
事实上,无论出现什么情况__exit__方法都会被执行,即使在with语句代码块内有异常被抛出。从上面你可以看到,被抛出的异常的类型,值和追踪栈都会被传递给__exit__这个方法。在上面的例子中,一个ZeroDivisionError被抛出。因此在开发库的时候,可以将一些清理资源、关闭文件等操作放在__exit__方法中。
总而言之,Python的with语句提供了更加简洁的代码,并使得出现异常时,一些处理操作更加简单方便。
原文:https://sdqali.in/blog/2012/07/09/understanding-pythons-with-statement/