前言

最早接触到with语句的时候,是初学python,对文件进行读写的时候,当时文件读写一般都是用open()函数来对文件进行读写,为了防止读写的过程中出现错误,也为了让代码更加的pythonic,会接触到with语句

with open('file.text', 'w') as f:
f.write('hello')

上面的代码仅需两行就实现了对文件进行写入的操作,很方便,代码也更整洁,不会出错。

事实上,上面一段代码就用到了上下文管理器的知识。

某种程度上,上下文管理器可以理解成try/finally的优化,使得代码更加易读,在通常情况下,我们读取文件的时候,如果不适用with语句,为了防止出错,可以采用try/finally的语句来进行读取,使得文件可以正常执行close()方法。

f = open('file.text', 'w'):
try:
f.write('hello')
finally:
f.close()

很明显,with语句比try/finally更易读,更友好。

上下文管理器

上下文管理器协议,是指要实现对象的 __enter__()__exit__() 方法。

上下文管理器也就是支持上下文管理器协议的对象,也就是实现了 __enter__()__exit__() 方法。

上下文管理器 是一个对象,它定义了在执行 with 语句时要建立的运行时上下文。 上下文管理器处理进入和退出所需运行时上下文以执行代码块。 通常使用 with 语句来使用,但是也可以通过直接调用它们的方法来使用。

简单来说,我们定义一个上下文管理器,需要在一个类里面一个实现__enter__(self) __exit__(self, exc_type, exc_value, traceback) 方法。

  • object.__enter__(self)

    进入与此对象相关的运行时上下文,并返回自身或者另一个与运行食上下文相关的对象。(with语句将会绑定这个方法的返回值到 as 子句中指定的目标)

  • object.__exit__(self, exc_type, exc_value, traceback)

    退出关联到此对象的运行时上下文。 各个参数描述了导致上下文退出的异常。 如果上下文是无异常地退出的,三个参数都将为None。如果提供了异常,并且希望方法屏蔽此异常(即避免其被传播),则应当返回真值。 否则的话,异常将在退出此方法时按正常流程处理。请注意__exit__()方法不应该重新引发被传入的异常,这是调用者的责任。如果 with_body 的退出由异常引发,并且__exit__()的返回值等于 False,那么这个异常将被重新引发一次;如果 __exit__() 的返回值等于 True,那么这个异常就被无视掉,继续执行后面的代码。

通常情况下,我们会使用with语句来使用上下文管理器:

with context_expr [as var]:
with_body

执行过程:

  1. 执行上下文表达式(context_expr)以获得上下文管理器对象。
  2. 加载上下文管理器对象的__exit__()方法,备用。
  3. 执行上下文管理器对象的__enter__()方法。
  4. 如果有as var语句,将__enter__()方法返回值绑定到 as 后面的 变量中。
  5. 执行 with 内的代码块(with_body)。
  6. 执行上下文管理器的__exit__()方法。

把文章开头的例子用上下文管理器实现一边:

class OpenFile(object):
def __init__(self, filename):
self.file = open(filename, 'w+') def __enter__(self):
return self.file def __exit__(self, exc_type, exc_val, exc_tb):
self.file.close() def main():
with OpenFile('text.txt') as f:
f.write('ok') if __name__ == "__main__":
main()

总结:在上下文管理器中,生成类实例的时候,会自动调用__enter__()方法,而在结束的时候,会自动调用__exit__()方法。

所以,在定义上下文管理器的时候,我们只需实现好这两个方法就行了。

上下文管理器的运用场景

上下文管理器的典型用法包括保存和恢复各种全局状态,锁定和解锁资源,关闭打开的文件等。

比如我们需要在一段代码中使用到数据库的查询,可以通过上下文处理器来优化我们的代码结构,

contextilb模块

contextilb模块是python内置模块中的一个用于上下文的模块,可以让我们更优雅地使用上下文管理器。

@contextmanager

这是contextlib模块提供的一个装饰器,用于将一个函数声明上下文管理,无需创建一个类或者单独的__enter__()方法和__exit__()方法,就可以实现上下文管理。

需要注意的是,被装饰的函数被调用的时候必须返回一个生成器,而且这个生成器只生成一个值,如果有as的话,该值讲绑定到with语句as子句的目标中。

from contextlib import contextmanager

@contextmanager
def tag(name):
print('<{}>'.format(name))
yield
print('</{}>'.format(name)) with tag('title'):
print("This is a contextmanger test")

输出为:

<title>
This is a contextmanger test
</title>

可以看出,输出的流程:

  1. 先输出yield前的输出语句;
  2. 然后再是tag()函数的输出语句,
  3. 最后是yield后面的输出语句。

在生成器函数中的yield之前的语句在__enter__()方法中执行,

相当于

def __enter__(self):
print('<{}>'.format(name)) def __exit__(self, exc_type, exc_val, exc_tb):
print('</{}>'.format(name))

closing

返回一个上下文管理器,在完成代码块的时候会关闭参数

源码参考:

class closing(AbstractContextManager):

    def __init__(self, thing):
self.thing = thing
def __enter__(self):
return self.thing
def __exit__(self, *exc_info):
self.thing.close()

常见用法,如写爬虫的时候,可以这样写:

from contextlib import closing
import requests url = 'http://www.baidu.com'
with closing(requests.get(url)) as page:
for line in page:
print(page)

上下文管理器查询数据库

代码:

import pymysql

class Database(object):
def __init__(self):
self.db = pymysql.connect("localhost", "root", "root", "test")
self.cursor = self.db.cursor() def query(self, sql):
self.cursor.execute(sql)
result = self.cursor.fetchone()
return result def __enter__(self):
return self def __exit__(self, exc_type, exc_val, exc_tb):
self.cursor.close()
self.db.close() def main():
sql = "SELECT password FROM USER WHERE username='{}' ORDER BY 1;".format('admin')
with Database() as s:
a = s.query(sql)
print(a) if __name__ == "__main__":
main()

使用contextlib模块编写:

class Database(object):
def __init__(self):
self.db = pymysql.connect("localhost", "root", "root", "test")
self.cursor = self.db.cursor() def query(self, sql):
self.cursor.execute(sql)
result = self.cursor.fetchone()
return result @contextmanager
def database_query():
q = Database()
yield q def main():
sql = "SELECT password FROM USER WHERE username='{}' ORDER BY 1;".format('admin')
with database_query() as s:
a = s.query(sql)
print(a) if __name__ == "__main__":
main()

最新文章

  1. C# 的界面控件属性修改线程安全问题
  2. PHP操作mysql数据库:[2]查询数据听语音
  3. servlet 中文乱码问题
  4. Angular JS笔记
  5. JSP过滤器Filter配置过滤类型汇总
  6. python3数据类型
  7. 常见资源记录定义(Resource Record)
  8. cocos2dx 动画 一
  9. 使用JDBC调用数据库的存储过程
  10. anthelion编译
  11. Android 友盟分享详细集成过程及所遇问题解决
  12. spark-2.4.0-hadoop2.7-安装部署
  13. log4j2 标签解析
  14. 苹果笔记本适合什么人 中国Mac电脑用户的8个事实
  15. What does -1 mean in numpy reshape?
  16. IOS中文本框输入自动隐藏和自动显示
  17. Android中利用C++处理Bitmap对象
  18. Windows平台编译memcached 1.2.6
  19. php header运用细节
  20. AutoResponder Reference

热门文章

  1. 让placeholder中的默认文字居中,或者缩进多少像素
  2. gin golang xorm
  3. img标签IE下有边距——2017/7/21
  4. Day92
  5. grant 命令
  6. mysql和mongodb的区别
  7. SKU和SPU表的设计
  8. malloc 和free例程
  9. 《Craking the Coding interview》python实现---02
  10. G4Studio+extjs+highcharts 下在ext4j的panel中放入hightCharts图表