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版本的代码:
with open("/tmp /foo.txt") as file:
data = file.read()
with如何工作?
这看起来充满魔法,但不仅仅是魔法,Python对with的处理还很聪明。基本思想是with所求值的对象必须有一个__enter__()方法,一个__exit__()方法。
紧跟with后面的语句被求值后,返回对象的__enter__()方法被调用,这个方法的返回值将被赋值给as后面的变量。当with后面的代码块全部被执行完之后,将调用前面返回对象的__exit__()方法。
下面例子可以具体说明with如何工作:
class Query(object):
def __init__(self, name):
self.name = name
def __enter__(self):
print('Begin')
return self
def __exit__(self, exc_type, exc_value, traceback):
print('exc_type:',exc_type,"\nexc_value:",exc_value,"\ntraceback:",traceback)
if exc_type:
print('Error:%s'%exc_type)
else:
print('End')
return True
def query(self):
print('Query info about %s...' % self.name)
with Query('Bob') as q:
q.query()
输出如下
Begin
Query info about Bob...
exc_type: None
exc_value: None
traceback: None
End
with Query('Bob') as q:
1 with Query('Bob') >> 触发__enter__(),拿到返回值
2.as q ----> q=返回值
with Query('Bob') as q 等同于 q= Query('Bob').__enter__()
4.执行代码块
①,没有异常情况下,整个代码块运行完成后触发__exit__,三个返回值都为None
②,有异常情况下,从异常出现的位置触发__eixt__
a.如果__exit__的返回值为True,代表吞吐了异常
b. 如果__exit__的返回值不为True,代表吐出了异常
c.__exit__的运行完成就代表了整个with语句的执行完毕
class Query(object):
def __init__(self, name):
self.name = name
def __enter__(self):
print('Begin')
return self
def __exit__(self, exc_type, exc_value, traceback):
print('exc_type:',exc_type,"\nexc_value:",exc_value,"\ntraceback:",traceback)
if exc_type:
print('Error:%s'%exc_type)
else:
print('End')
return True
def query(self):
q2
print('Query info about %s...' % self.name)
with Query('Bob') as q:
q.query()
## 运行:
Begin
exc_type: <class 'NameError'>
exc_value: name 'q2' is not defined
traceback: <traceback object at 0x0000010CC4F8D648>
Error:<class 'NameError'>
去掉 return True 报错
Begin
Traceback (most recent call last):
exc_type: <class 'NameError'>
File "F:/django/alipay/check/322.py", line 49, in <module>
exc_value: name 'q2' is not defined
q.query()
traceback: <traceback object at 0x0000028EB2C4D648>
File "F:/django/alipay/check/322.py", line 44, in query
Error:<class 'NameError'>
q2
NameError: name 'q2' is not defined
@contextmanager
编写__enter__和__exit__仍然很繁琐,因此Python的标准库contextlib提供了更简单的写法,上面的代码可以改写如下:
from contextlib import contextmanager class Query(object): def __init__(self, name): self.name = name def query(self): print('Query info about %s...' % self.name) @contextmanager def create_query(name): print('Begin') q = Query(name) yield q print('End')
@contextmanager这个decorator接受一个generator,用yield语句把with ... as var把变量输出出去,然后,with语句就可以正常地工作了:
with create_query('Bob') as q: q.query()
很多时候,我们希望在某段代码执行前后自动执行特定代码,也可以用@contextmanager实现。例如:
@contextmanager def tag(name): print("<%s>" % name) yield print("</%s>" % name) with tag("h1"): print("hello") print("world") 上述代码执行结果为: <h1> hello world </h1>
代码的执行顺序是:
- with语句首先执行yield之前的语句,因此打印出<h1>;
- yield调用会执行with语句内部的所有语句,因此打印出hello和world;
- 最后执行yield之后的语句,打印出</h1>。
因此,@contextmanager让我们通过编写generator来简化上下文管理。