• 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)

  • 相关阅读:
    jquery 父、子页面之间页面元素的获取,方法的调用
    读excle
    dataTable写入数据库(大数据写入)
    经典类和新式类的区别
    重写父类方法
    封装redis
    继承
    私有方法
    优化MyDb

  • 原文地址:https://www.cnblogs.com/kainhuck/p/11111524.html
Copyright © 2020-2023  润新知