Python 上下文(Context)学习笔记
前言
最早接触到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
执行过程:
- 执行上下文表达式(context_expr)以获得上下文管理器对象。
- 加载上下文管理器对象的
__exit__()
方法,备用。 - 执行上下文管理器对象的
__enter__()
方法。 - 如果有
as var
语句,将__enter__()
方法返回值绑定到 as 后面的 变量中。 - 执行 with 内的代码块(with_body)。
- 执行上下文管理器的__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>
可以看出,输出的流程:
- 先输出
yield
前的输出语句; - 然后再是
tag()
函数的输出语句, - 最后是
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()
推荐阅读
-
Python学习笔记整理3之输入输出、python eval函数
-
Python中Random和Math模块学习笔记
-
Python学习笔记之读取文件、OS模块、异常处理、with as语法示例
-
Python学习笔记之os模块使用总结
-
Python学习笔记(一)(基础入门之环境搭建)
-
Python ORM框架SQLAlchemy学习笔记之数据添加和事务回滚介绍
-
python网络编程学习笔记(10):webpy框架
-
Python ORM框架SQLAlchemy学习笔记之关系映射实例
-
python网络编程学习笔记(六):Web客户端访问
-
python网络编程学习笔记(八):XML生成与解析(DOM、ElementTree)