python面試專題--with 關鍵字與上下文管理

本文首發於公衆號「zone7」,關注獲取最新推文!python

  • 瞎比比
  • 聊聊爲何要用 with、contextlib
  • 舉個栗子
  • with、contextlib 原理
  • 常見的 with 用例

瞎比比

本文源碼地址:mysql

https://github.com/zonezoen/blog/tree/master/python/interview
複製代碼

最近秋招就要到了,我準備了 Python 面試的一系列專題,涉及到 Python 的一些高級用法,例如:垃圾回收機制、裝飾器、上下文管理器等等。但願在秋招路上助你一臂之力。git

聊聊爲何要用 with、contextlib

一般狀況下,咱們在作一些資源操做的時候,都要作 open 和 close 等相關的操做,爲的就是使用完資源以後,及時清理不用的資源,避免佔用內存。例如,典型的數據庫操做:github

conn = pymysql.connect()
cur = conn.cursor()
sql = "INSERT INTO `users` (`name`, `password`, `age`, `sex`) VALUES (%s, %s, %s, %s)"
cur.execute(sql)
conn.commit()
cur.close()
conn.close()
複製代碼

若是咱們常常操做一些資源,那是否是就得常常作一些重複的操做?甚至有時候,咱們忘記進行資源回收操做,致使內存泄露,或許可能會致使線上事故。那咱們有什麼操做可以處理這些麻煩,或者說有什麼一勞永逸的方法嗎?答案是有的,正是此文的標題,且往下看。面試

舉個栗子

我先來舉個栗子吧,看看這個 with、contextlib 到底是怎麼方便的,且看下面的代碼。建議你在看完 with、contextlib 原理以後,再一次回看這些栗子,你會有收穫的。sql

class MysqlDb():
    def __init__(self, database, host="localhost", user="root", prot=3306, password="root", charset="utf8mb4"):
        self.conn = pymysql.connect(host=host, user=user, password=password, port=prot, database=database,
                                    cursorclass=pymysql.cursors.DictCursor, charset=charset)
        self.cur = self.conn.cursor()

    def __enter__(self):
        return self.cur

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.conn.commit()
        self.cur.close()
        self.conn.close()


with MysqlDb(database="test", ) as db:
    sql = "select * from users"
    db.execute(sql)
    print(db.fetchone())
    intert_sql = "INSERT INTO `users` (`name`, `password`, `age`, `sex`) VALUES (%s, %s, %s, %s)"
    db.execute(intert_sql, ('zone7', 'pwd', "18", "man"))


@contextlib.contextmanager
def get_mysql_cur(database, host="localhost", user="root", prot=3306, password="root", charset="utf8mb4"):
    conn = pymysql.connect(host=host, user=user, password=password, port=prot, database=database,
                           cursorclass=pymysql.cursors.DictCursor, charset=charset)
    cur = conn.cursor()
    yield cur
    conn.commit()
    cur.close()
    conn.close()


with get_mysql_cur(database="test", ) as db:
    sql = "select * from users"
    db.execute(sql)
    print(db.fetchone())
複製代碼

結果:數據庫

{'age': '18', 'name': 'zone', 'sex': 'man', 'id': 1, 'password': '123'}
複製代碼

with、contextlib 原理

在看完栗子以後,是否是以爲很方便?建議自行跑一下代碼,將 host、帳號、密碼等必要信息修改爲本身的。須要說明下的是,這個應用仍是比較普遍的,不是僅僅侷限於數據庫中的應用。 那麼接下來咱們就來講說其中的原理。bash

class MysqlDb():
    def __init__(self, database, host="localhost", user="root", prot=3306, password="root", charset="utf8mb4"):
        self.conn = pymysql.connect(host=host, user=user, password=password, port=prot, database=database,
                                    cursorclass=pymysql.cursors.DictCursor, charset=charset)
        self.cur = self.conn.cursor()

    def __enter__(self):
        return self.cur

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.conn.commit()
        self.cur.close()
        self.conn.close()
複製代碼

咱們在調用 with 的時候,首先代碼會先跑到 init 方法,會初始化一些必要的對象。而後運行 enter 方法,它的返回值會被賦值給 as 關鍵字後面的對象,就像栗子中的 as db。而後咱們就能夠經過拿到的對象進行一些增刪改查的操做了。最後業務代碼運行結束以後,會調用 exit 方法,用來執行回收資源的操做。執行順序是:socket

init --> enter --> 業務代碼 --> exit
複製代碼

注意:enter 能夠返回元組,且元組必須使用(),這樣就可處理多個對象了。 須要着重介紹一下的是 exit 方法,該方法中有3個參數,其功能以下:fetch

exc_type: 錯誤的類型 
exc_val: 錯誤類型對應的值 
exc_tb: 代碼中錯誤發生的位置 
複製代碼

若是 exit 方法返回 false (不寫返回值時,默認返回 false)則會將異常拋出給 with 語句以後的代碼來處理異常。若是返回 True ,則須要在 exit 方法中處理異常。

@contextlib.contextmanager
def get_mysql_cur(database, host="localhost", user="root", prot=3306, password="root", charset="utf8mb4"):
    conn = pymysql.connect(host=host, user=user, password=password, port=prot, database=database,
                           cursorclass=pymysql.cursors.DictCursor, charset=charset)
    cur = conn.cursor()
    yield cur
    conn.commit()
    cur.close()
    conn.close()
複製代碼

關於 contextlib ,主要是你要理解 yield 的用法,這裏不過多講解 yield。在 yield 以前的代碼至關於 init 和 enter 的操做,yield 後面的值,至關於 enter 返回的值,yield 下面的代碼至關於 exit 方法。

常見的 with 用例

file
db
socket
decimal.Context
thread.LockType
threading.Lock
threading.RLock
threading.Condition
threading.Semaphore
threading.BoundedSemaphore
複製代碼

今天就講到這,咱們一步一個腳印,加油!

相關文章
相關標籤/搜索