Python上下文管理器

上下文管理器

1.与装饰器的区别

上下文管理器是装饰器的近亲,装饰器用于包装函数,上下文管理器用于包装任意代码块.

上下文管理器最常用的场合--作为确保资源被正确清理的一种方式.

2.上下文管理器举例

打开文件-打开文件必须确保其能关闭,这就构成了一种上下文的关系

try:
    f = open("test.txt")
    contents = f.read()
finally:
    f.close()	# 无论如何文件必须确保关闭

3.with语句

我们知道with语句打开文件会自动帮我们关闭文件,这个with语句就起到了上下文管理器的作用.下面会介绍原理.

with open("test.txt", "r") as f:
    contents = f.read()

3.__enter____exit__方法

with语句会调用对象的__enter____exit__方法,在上面的例子中open就是一个对象(Python中一切皆对象).__enter__方法的返回值会赋值给后面的变量.__enter__就是上文管理__exit__就是下文管理.

写一个自己的上下文管理器.

class MyOpen(object):
    def __init__(self, path, method):
        self.file = open(path, method)

    def __enter__(self):
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.file.close()


with MyOpen("ghostdriver.log", "r") as f:
    contents = f.readlines()

后面会解释__exit__方法中的后三个参数

4.with调用__enter____exit__的过程

In [21]: class ContextManager(object): 
    ...:     def __init__(self): 
    ...:         self.entered = False 
    ...:     def __enter__(self): 
    ...:         self.entered = True 
    ...:     def __exit__(self, exc_type, exc_val, exc_tb): 
    ...:         self.entered = False 
    ...:          
    ...:                                                                                          

In [22]: cm = ContextManager()                                                                    

In [23]: print(cm.entered)                                                                        
False

In [24]: with cm: 
    ...:     print(cm.entered) 
    ...:                                                                                          
True

In [25]: print(cm.entered)                                                                        
False


使用with语句会先调用with后面类的__enter__方法,with语句块结束后会调用__exit__方法

In [26]: class ContextManager(object): 
    ...:     def __init__(self): 
    ...:         self.entered = False 
    ...:     def __enter__(self): 
    ...:         self.entered = True 
    ...:         print("调用__enter__") 
    ...:     def __exit__(self, exc_type, exc_val, exc_tb): 
    ...:         self.entered = False 
    ...:         print("调用__exit__") 
    ...:          
    ...:          
    ...:                                                                                          

In [27]: cm = ContextManager()                                                                    

In [28]: with cm: 
    ...:     ... 
    ...:                                                                                          
调用__enter__
调用__exit__

5.处理异常

__exit__方法可以捕获包装代码块中的异常.

__exit__函数中的参数解释一下:

  1. exc_type :异常类型
  2. exc_val:异常实例
  3. exc_tb:回溯

如果没有异常则以上三个参数均为None

__exit__对异常的处理:

  1. 返回True终止异常
  2. 返回False传播异常

什么都不return:

class ContextManager(object):
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(exc_type)
        print(exc_val)
        print(exc_tb)


with ContextManager():
    1 / 0

output:

<class 'ZeroDivisionError'>
division by zero
<traceback object at 0x7f3a97015cc8>
Traceback (most recent call last):
  File "/home/kain/PycharmProjects/Python_code/2019暑假/05-上下文管理器-异常.py", line 15, in <module>
    1 / 0
ZeroDivisionError: division by zero

return True:

class ContextManager(object):
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(exc_type)
        print(exc_val)
        print(exc_tb)
        return True


with ContextManager():
    1 / 0

output:

<class 'ZeroDivisionError'>
division by zero
<traceback object at 0x7f3a222e3d48>

return False:

class ContextManager(object):
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(exc_type)
        print(exc_val)
        print(exc_tb)
        return False


with ContextManager():
    1 / 0

output:

<class 'ZeroDivisionError'>
division by zero
<traceback object at 0x7f268176fc48>
Traceback (most recent call last):
  File "/home/kain/PycharmProjects/Python_code/2019暑假/05-上下文管理器-异常.py", line 16, in <module>
    1 / 0
ZeroDivisionError: division by zero

6.使用场景

关闭资源

比如连接数据库时必须要关闭数据库,像这种打开后必须关闭的操作可以用上下文管理器的操作.

Python连接MySQL数据库的上下文管理器例子:

import pymysql


class Mysql(object):
    def __init__(self, host, username, passwd, database):
        self.host = host
        self.username = username
        self.passwd = passwd
        self.database = database
        self.cursor = None

    def __enter__(self):
        self.db = pymysql.connect(self.host, self.username, self.passwd, self.database)
        self.cursor = self.db.cursor()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_val:
            # 如果出现异常
            print("An Exception: %s." % exc_val)

        self.db.close()
        return True

    def executeSql(self, sql):
        self.cursor.execute(sql)
        results = self.cursor.fetchall()
        return results

with Mysql("127.0.0.1", "root", "kaindb", "test") as m:
    results = m.executeSql("SELECT * FROM users;")

    for each in results:
        print(each)

output:

('admin', '123456')
('kainhuck', '123456')

处理异常

见上条内容

7.contextlib.contextmanager装饰器

可以利用这个装饰器将一个函数变成上下文管理器

import contextlib


@contextlib.contextmanager
def contextManager():
    try:
        yield
    except Exception as e:
        print(e)


if __name__ == '__main__':
   with contextManager():
       1 / 0

output:

division by zero

注意:被装饰的函数需要返回单个值(yield)

posted @ 2019-06-30 22:24  KainHuck  阅读(208)  评论(0编辑  收藏  举报