妙用Python上下文管理器

136 阅读3分钟

Python的上下文管理器,根据其定义,指实现上下文管理协议的方法的对象。主要用于安全的释放资源。核心是实现__enter__和__exit__两个方法,使用with关键字进行调用。 在工作中经常遇到资源的打开与释放,想到上下文管理器可以实现这种功能,特地做的学习和研究。

1.我们最常见的上下文管理是文件的打开关闭

如果不用上下文管理器,一个文件的打开和关闭可能是这样的

f = open( 'abc.txt' , 'w' ,encoding= 'utf-8' )

f.close()

  为了确保关闭,我们可能会使用异常捕获语句

try:  
    f = open( 'abc.txt' , 'w' ,encoding= 'utf-8' )  
    f.write( 'xxx' )  
finally:  
    f.close()

但这样的语句部分编译器会提示你f可能是未被定义的,比如Pycharm会标黄。因为前面f一旦出错,就没有定义成功f,何来关闭这一说法,f关闭的这个位置依然会出错。

此时我们可以使用上下文管理器

with open( 'abc.txt' , 'w' ,encoding= 'utf-8' ) as f:  
    f.write( 'xxx' )

2. 第二个常见的上下文是线程锁

使用线程锁来处理特定的文件时,可以确保文件不会被同时写入或读取,功能取决于你选择的锁类型。

import threading  
my_lock = threading.Lock()  
with my_lock:  
    print(123)

3. 数据库形式的上下文管理器

还记得mysql的连接吗,常见的流程如下,要先创建连接和游标,再执行语句后提交,最后关闭游标和连接。

import pymysql  
  
conn = pymysql.connect(user='root', host='127.0.0.1', password='123456', port='3306', database='test_db')  
cursor = conn.cursor()  
cursor.execute('xxx')  
conn.commit()  
cursor.close()  
conn.close()

此时我们可以将其改造成上下文管理器类。

class MySQLConnector:  
    def __init__(self):  
        self.user = 'root'  
        self.password = '123456'  
        self.host = '127.0.0.1'  
        self.database = 'test_db'  
        self.port = 3306  
        self.conn = None  
        self.cursor = None  
  
    def __enter__(self):  
        try:  
            self.conn = pymysql.connect(  
                user=self.user, password=self.password, host=self.host, database=self.database, port=self.port  
            )  
            self.cursor = self.conn.cursor()  
        except Exception as e:  
            traceback.print_exc()  
            raise e  
  
    def __exit__(self, exc_type, exc_val, exc_tb):  
        try:  
            self.conn.commit()  
        except Exception as e:  
            traceback.print_exc()  
            raise e  
        finally:  
            self.cursor.close()  
            self.conn.close()

  使用的时候如下,其中cur就是我们在其中编写的enter方法的返回值,如果没有返回值,像上面的线程锁,就不需要as。

with MySQLConnector as cur:  
    cur.execute( 'xxx' )

上面的还可以做成带参数的类型,参数也可以用函数的**参数压缩一下成一个字典传进去。上下文管理器类中涉及的修改如下:

def init(self, config):
self.config = config

self.conn = pymysql.connect(**config)

使用时:

config = {  
    'user': 'root',  
    'password': '123456',  
    'host': '127.0.0.1',  
    'database': 'test_db',  
    'port': 3306  
}  
with MySQLConnector(config) as cur:  
    cur.execute('xxx')

这样就做成了一个通用的可以自己增加配置的类,还可以自行增加mysql的参数,比如字符集、游标类型这种。

4. 常用于服务器管理的连接类型的上下文管理器

paramiko是常用的服务器连接库,可以执行远程指令,上传下载文件等操作。主要用到的有SSHClient、Transport和SFTPClient三种方式。

这里对Transport方式作上下文管理器的实现

class MyTrans:  
    def __init__(self,config):  
        self.config = config  
        self.trans = None  
      
    def __enter__(self):  
        try:  
            self.trans = paramiko.Transport((self.config.get('host'),self.config.get('port')))  
            self.trans.connect(username=self.config.get('user'),password=self.config.get('password'))

            return self.trans
 
        except Exception as e:  
            traceback.print_exc()  
            raise e  
          
    def __exit__(self, exc_type, exc_val, exc_tb):  
        try:  
            self.trans.close()  
        except Exception as e:  
            traceback.print_exc()  
            raise e

同理可得到一个SFTP的上下文管理器

class MySFTP:  
    def __init__(self, trans):  
        self.trans = trans  
        self.sftp = None  
  
    def __enter__(self):  
        try:  
            self.sftp = paramiko.SFTPClient.from_transport(self.trans)  
            return self.sftp  
        except Exception as e:  
            traceback.print_exc()  
            raise e  
  
    def __exit__(self, exc_type, exc_val, exc_tb):  
        try:  
            self.sftp.close()  
        except Exception as e:  
            traceback.print_exc()  
            raise e

两者是联动使用的,使用方式如下:

with MyTrans(config) as trans:  
    with MySFTP(trans) as sftp:  
        sftp.put( 'xxx' , 'xxx' )

以上就是几种上下文管理器的常见用法,相信大家看完之后能有一定的提升!