本文首發於公衆號「zone7」,關注獲取最新推文!python
本文源碼地址:mysql
https://github.com/zonezoen/blog/tree/master/python/interview
複製代碼
最近秋招就要到了,我準備了 Python 面試的一系列專題,涉及到 Python 的一些高級用法,例如:垃圾回收機制、裝飾器、上下文管理器等等。但願在秋招路上助你一臂之力。git
一般狀況下,咱們在作一些資源操做的時候,都要作 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'}
複製代碼
在看完栗子以後,是否是以爲很方便?建議自行跑一下代碼,將 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 方法。
file
db
socket
decimal.Context
thread.LockType
threading.Lock
threading.RLock
threading.Condition
threading.Semaphore
threading.BoundedSemaphore
複製代碼
今天就講到這,咱們一步一個腳印,加油!