{%hackmd @wh91008/theme %} # Lecture 2 - Python 與 Firestore 連結之實用資訊 ###### tags: `GCP` `Firestore` `Python` [TOC] --- ## ==一、前言== 從 [第一章 - 使用Python建置Firestore](https://hackmd.io/QOoc_JOOSkCe27Iyr4vryg) 我們已經學會了如何使用 Python 來建置 Firestore 與使用本機端新增、更改、查詢和刪除資料。這個章節主要是用來分享有哪些是在撰寫程式時很好用的函數 --- ## ==二、函數列表== - <code>items()</code>:以列表返回可遍歷的(鍵, 值) 元組數組。 - <code>batch()</code>:批次處理函數 - <code>(u'str')</code>:轉換字串編碼 (字串格式化) - <code>list.extend()</code>:在list尾端一次性追加另一個list中的多個值。 - <code>import asyncio</code>:異步協程 ---- ## ==三、函數介紹== ### (一)```items()``` 例題如下: dict原為字典形式,透過items()函數,可將字典的鍵與值分開並且返回成列表 ```javascript= dict = {'Name': 'Runoob', 'Age': 7} dict.items() ``` 執行結果 ``` dict_items([('Name', 'Runoob'), ('Age', 7)]) ``` ### (二)```db.batch()``` 即「批次處理」。可執行```set()```、```update```、```delete()```,(但不能執行```get()```) > 簡介: 把大量的步驟切割成小步驟後,再一次進行,可以用來減少來回等候的時間。 > 使用方式: 透過 Firestore.batch() 去建立一個 WriteBatch 物件,用該物件來記錄要批次操作的寫入動作。 > 優點: 在於「多個寫入操作」只需「使用一次 request 」完成(即最後一行程式碼打上batch.commit()),減少來回等候的時間。這種功能非常適合用在需要大量遷移資料到 Cloud Firestore 的情境。一個 BatchWrite 最多可以包含五百個寫入操作。 例題如下(承接基礎操作): ```python= #####################一定需要,且一段程式碼只能出現一次#################### import firebase_admin # 用來認證 from firebase_admin import credentials # path/to/serviceAccount.json 須改為自己的金鑰路徑 cred = credentials.Certificate("path/to/serviceAccount.json") firebase_admin.initialize_app(cred, { 'projectId': 'fir-test-84116', }) # 用來存放資料 from firebase_admin import firestore db = firestore.client() ######################################################################## # 原先使用的資料新增/修改/查詢/刪除方法(需分開打,否則一增一刪會看不出來) # collectio為集合,document為要寫入的文件 # # collectio為集合,document為要寫入的文件 # student1 = db.collection('Q1').document('DOC2') # student1.set({ # '姓名': '西野', # '年齡': '23', # '學號': '123456789' # }) # # # Update Document # student1.update({ # '年齡': '24' # }) # # # Get Collection # get_collection = db.collection("Q1").get() # # # Delete Collection # for delete_list in get_collection: # doc_del = db.collection('Q1').document(delete_list.id) # doc_del.delete() # print('Delete Doc: {} Complete.'.format(delete_list.id)) ######################################################################## # 使用db.batch進行批次處理 # 先新增collection、document與增設batch() batch = db.batch() student1 = db.collection('Q1').document('DOC2') # 設定批次處理的流程batch.行為(目標物,{"增設條件"}) # 新增(batch版) batch.set(student1, { '姓名': '西野', '年齡': '23', '學號': '123456789' }) # 修正(batch版) batch.update(student1, {"年齡": "24"}) # 查詢(不可使用batch) get_collection = db.collection("Q1").get() # 刪除(batch版) for delete_list in get_collection: batch.delete(db.collection('Q1').document(delete_list.id)) print('Delete Doc: {} Complete.'.format(delete_list.id)) # 執行至此條程式碼時,功能才會全部一起運作 batch.commit() ``` :::warning :bulb: 小提醒:如果```set()```為batch、而```delete()```非batch,則會導致程式碼是先運作```delete()```之後才一次運作```set()```,所以會造成資料無法刪除(因為刪了又馬上被加回來),所以即使刪減的資料是不同筆,還是不建議一個batch一個非batch(一致比較好)。 ::: ###### :book: 參考資料:https://ithelp.ithome.com.tw/users/20103676/ironman/2080 ### (三)字串編碼轉換 Python3默認字符串編碼方式為unicode,而在連接firebase時可能編碼會亂掉,所以建議於字串前面+```u```。(補:Python2為bytes編碼) 例如 <code>doc_ref = db.collection(u'users').document()</code> ,即指定users為unicode,額外解釋如下: - u/U:表示unicode字串(常用於英文之外之語言) 代表是對字串進行unicode編碼(不只可用在中文,還可以針對任何的字串) :::info 一般英文字元基本上可在使用各種編碼下正常解析,所以一般不帶u;但若使用中文,建議附上u,否則一旦編碼轉換就會出現亂碼。 ::: - r/R:非轉義的原始字串(常用於正則表示式) 與普通字元相比,其他相對特殊的字元可能包含轉義字元(即反斜槓加上有些對應字母就會有特殊含義,比如最常見的"\n"表示換行,"\t"表示Tab...等)。 如果是以r開頭,則可代表後面的字元都是普通的字元("\n"原指換行,而r"\n"則沒有特殊含義) :::info 也可用在```read.csv(r"路徑.csv")```,且較為嚴謹不易出錯。 ::: - b:bytes 字串前面+b,即轉換為bytes格式 :::info 在Python2.x裡, b字首沒什麼具體意義,只是為了方便相容Python3.x ::: ```python= # 例子 type(u'元') # <class 'str'> type(r'元') # <class 'str'> type(b'元') # <class 'bytes'> # 也可使用'元'.encode("???")來指定編碼,常見編碼如big5、UTF-8... # 而str和bytes轉換也可使用bytes('元',encoding='???')和 # str('元',encoding='???'),encoding為原來'元'的編碼 ``` ###### :book: 延伸閱讀與參考資料: ###### - python字串前面加u,r,b的含義 https://www.itread01.com/content/1546758031.html ###### - UTF-8與unicode的差異 https://dotblogs.com.tw/jimmyyu/archive/2009/08/18/10113.aspx ###### - Python編碼轉換方法 https://openhome.cc/Gossip/Encoding/Python.html ###### - Python中str與bytes轉換 https://blog.csdn.net/weixin_39406669/article/details/79524227 ### (四)```list.extend()``` 在aList的尾端新增blist的所有值,則輸入<code>aList.extend(bList)</code>,範例如下: ```python= aList = [123, 'xyz', 'zara', 'abc', 123] # 原始list bList = [2009, 'manni'] # 想在尾端新增的list aList.extend(bList) print ("Extended List : ", aList) ``` 以上實例輸出結果如下: ``` Extended List : [123, 'xyz', 'zara', 'abc', 123, 2009, 'manni'] ``` ###### 參考資料:http://www.runoob.com/python/att-list-extend.html ### (五)異步協程 簡單來說,異步協程就是讓電腦可以達到分工的效果,節省資源 - 異步協程的存在理由主要有兩個: - 有多個任務需要同時執行 現在電腦常常都有6核心、8核心的CPU,所以通常1核心的CPU要同時處理多件事情,指要讓各個任務穿插執行,就能讓人感覺好像是同時進行一樣,像下面這張圖: ![](https://i.imgur.com/oQfYyKA.png =300x120) task1如果一次搶走所有資源(黑方塊呈現連續),則task2必須得等到task1執行結束後才能進行(就像用軟體放音樂的同時,居然不能打字一樣),因此讓兩個任務彼此穿插(圖片看起來可能間距很大,實際上運行的動作只有幾毫秒),讓使用者察覺不到其實任務是穿插進行。 ###### :page_facing_up: 圖片來源:https://laike9m.com/blog/huan-zai-yi-huo-bing-fa-he-bing-xing,61/ - io調度時間比起CPU執行指令的時間過長 io時間比CPU執行時間要花費更久,所以為了不要讓CPU去等待io而浪費時間,我們就可以趁這段時間去切換執行其他任務。 :::warning :bulb: 小提醒:I/O,即輸入/輸出,通常指資料在記憶體和外部記憶體或其他周邊裝置(如主機、螢幕、USB、ADSL...等)之間的輸入和輸出。因此I/O時間過長,指的就是電腦把時間都花費在記憶體和裝置的資源交換上。 詳細可參考 [網址](https://ithelp.ithome.com.tw/articles/10199403) ::: 由於上述幾個兩個重大理由,所以異步協程誕生了 - 異步協程的幾項基本概念: - 事件迴圈(event loop) 異步協程可以在多個任務之間切換(程式的架構裡一定有一個任務的list,讓電腦知道有哪些動作要執行),而任務的list以及中間切換的機制,就是由事件迴圈(Event loop)來處理。 - 事件(event) 即要處理的任務 - 回調函數(callback) 假如協程是一個 IO 的「讀」操作,等它讀完數據後,我們希望得到「通知」,以便下一步數據的處理。這一需求可以通過往 future 添加「回調」來實現。 - 正式練習: - 定義協程(corutine)只需要在<code>def</code>前面加上<code>async</code>即可。 - 協程不能直接運行,需要把協程加入到事件循環(loop),由後者在適當的時候調用協程。 - 在使用<code>asyncio.sleep(n)</code>(代表中斷n秒)時,使用<code>await</code>可以讓電腦讓出控制權。 - 如果要使用asyncio來讓事件一起發生,就需要多個協程來完成任務,每當有任務阻塞的時候就await,其他協程繼續工作 - <code>asyncio.ensure_future()</code>把協程封裝成一個task對象,這個對象可以儲存任務執行時的狀態與環境。(就像是把遊戲存檔一樣) ```python= import asyncio loop = asyncio.get_event_loop() #建立一個Event Loop async def example1(): # 定義一個中間會被中斷的協程 print("Start example1 coroutin.") await asyncio.sleep(1) # 中斷協程一秒 print("Finish example1 coroutin.") async def example2(): # 定義一個協程 print("Start example2 coroutin.") # do some process... print("Finish example2 coroutin.") tasks = [ # 建立一個任務列表 asyncio.ensure_future(example1()), asyncio.ensure_future(example2()), ] loop.run_until_complete(asyncio.wait(tasks)) ``` :::warning :bulb: **小提醒** 如果調用 `time.sleep(5)` ,程序會完成的停止 5 秒才會繼續進行之後的程序。 不過如果調用 `await asyncio.sleep(5)` 時,它會要求事件循環在你的 `await` 語句完成執行時運行其他東西(這段時間內可以有其他事件循環在裡面執行,就是穿插的概念)。 ::: ###### :book: 參考資料: ###### 1.異步執行的好處 https://ithelp.ithome.com.tw/articles/10199385 ###### 2.異步程式設計基本概念 https://ithelp.ithome.com.tw/articles/10199403 ###### 3.建立Event Loop和定義協程 https://ithelp.ithome.com.tw/articles/10199408 ###### 4.很不錯的教學 https://zhuanlan.zhihu.com/p/27258289