## 資料結構 資料結構是什麼? 答:實際上就是幫助你有效率去存取、修改、新增、整理資料的方法 想像以下場景: 今天做了一個可以自動批改學生成績的程式,你會需要以下步驟來進行 1. 從數位學苑中獲取全部學生的考卷 2. 讀取每個考卷,根據答案進行批改 3. 將每位學生的成績傳回去到數位學苑 解釋一下每個步驟理論上會用到甚麼 1. 獲取考卷且儲存:爬蟲獲取(之後會教)、List儲存 2. 批改:List存取 3. 上傳回去:爬蟲上傳、List存取 這裡可以看到List的使用場景 就是存放同性質的資料到一個地方 ![image](https://hackmd.io/_uploads/SyVXunTJbx.png) Source:https://www.analyticsvidhya.com/blog/2021/06/functions-you-should-know-to-master-lists-in-python/ 所以如果沒有使用List來儲存的話 你就會這樣寫 ```python= student1_exam = get_exam("student1") student2_exam = get_exam("student2") student3_exam = get_exam("student3") ``` 這樣的缺點非常明顯:不靈活 透過List寫的話: ```python= # 直接把一整個列表爬下來 student_exams = get_all_exams() for student in student_exams: # 開始批改 ``` 這樣就變成非常靈活 接下來開始介紹python中比較常見的資料結構和他們的使用場景 ### List(列表) #### 作用: 如同前面提到的,List (列表) 就像一個收納盒,主要作用是將一群資料(元素)依照「順序」儲存在一起 ```python= # 建立一個儲存成績的 List scores = [85, 92, 78, 95, 88] # 建立一個空的 List,之後再慢慢新增資料 students = [] ``` 使用索引(index)來取得特定位置的元素。索引從 0 開始 ```python= scores = [85, 92, 78, 95, 88] # 取得第一個學生的成績 (索引 0) first_score = scores[0] print(f"第一個成績: {first_score}") # Output: 第一個成績: 85 # 取得第三個學生的成績 (索引 2) third_score = scores[2] print(f"第三個成績: {third_score}") # Output: 第三個成績: 78 # 也可以從後面開始算 (索引 -1 是最後一個) last_score = scores[-1] print(f"最後一個成績: {last_score}") # Output: 最後一個成績: 88 ``` 因為 List 是可變的,你可以直接透過索引重新賦值 ```python= scores = [85, 92, 78, 95, 88] print(f"修改前的成績: {scores}") # 老師發現改錯了,第一個學生的成績其實是 87 scores[0] = 87 print(f"修改後的成績: {scores}") # Output: 修改後的成績: [87, 92, 78, 95, 88] ``` 最常用的是使用 .append() 方法,將新元素加到 List 的「尾端」 ```python= scores = [87, 92, 78] print(f"新增前: {scores}") # 新來一位轉學生,成績 90 分 scores.append(90) print(f"新增後: {scores}") # Output: 新增後: [87, 92, 78, 90] ``` 刪除元素,有幾種方式,最常用的是 .remove() (刪除第一個符合的值) 或 del (刪除指定索引的元素) ```python= scores = [87, 92, 78, 90, 92] # 假設有兩個 92 分 # 情況一:想刪除 78 分 (使用 .remove()) scores.remove(78) print(f"刪除 78 分後: {scores}") # Output: [87, 92, 90, 92] # 情況二:想刪除索引為 1 的元素 (也就是第一個 92 分) (使用 del) del scores[1] print(f"刪除索引 1 後: {scores}") # Output: [87, 90, 92] ``` 使用 len() 函式來知道 List 中有多少個元素 ```python= scores = [87, 90, 92] count = len(scores) print(f"總共有 {count} 位學生成績") # Output: 總共有 3 位學生成績 ``` 遍歷,這就是前面「批改考卷」範例中最重要的應用:使用 for 迴圈一個一個取出 List 中的所有元素來處理 ```python= scores = [87, 92, 78, 95, 88] print("--- 開始列印成績單 ---") for score in scores: # score 這個變數會依序變成 87, 92, 78... if score < 90: print(f"學生成績: {score} (加油!)") else: print(f"學生成績: {score} (表現優良!)") ``` 這裡可以看到其他更多的資料結構 https://medium.com/@gdgntpu/%E7%A4%BE%E8%AA%B2%E5%9B%9E%E9%A1%A7-python-%E5%85%A5%E9%96%80-4-%E8%B3%87%E6%96%99%E7%B5%90%E6%A7%8B-10f963157164 ## 函式 可以理解成可重複利用的程式碼區塊 直接看例子比較快 ```python print("Hello world") ``` 一切看起來很美好對吧 那如果我告訴你實際上這個**簡單**的函式背後的樣子長這樣呢? :::spoiler ```c= static PyObject * builtin_print_impl(PyObject *module, PyObject * const *objects, Py_ssize_t objects_length, PyObject *sep, PyObject *end, PyObject *file, int flush) /*[clinic end generated code: output=38d8def56c837bcc input=ff35cb3d59ee8115]*/ { int i, err; if (file == Py_None) { file = PySys_GetAttr(&_Py_ID(stdout)); if (file == NULL) { return NULL; } /* sys.stdout may be None when FILE* stdout isn't connected */ if (file == Py_None) { Py_DECREF(file); Py_RETURN_NONE; } } else { Py_INCREF(file); } if (sep == Py_None) { sep = NULL; } else if (sep && !PyUnicode_Check(sep)) { PyErr_Format(PyExc_TypeError, "sep must be None or a string, not %.200s", Py_TYPE(sep)->tp_name); Py_DECREF(file); return NULL; } if (end == Py_None) { end = NULL; } else if (end && !PyUnicode_Check(end)) { PyErr_Format(PyExc_TypeError, "end must be None or a string, not %.200s", Py_TYPE(end)->tp_name); Py_DECREF(file); return NULL; } for (i = 0; i < objects_length; i++) { if (i > 0) { if (sep == NULL) { err = PyFile_WriteString(" ", file); } else { err = PyFile_WriteObject(sep, file, Py_PRINT_RAW); } if (err) { Py_DECREF(file); return NULL; } } err = PyFile_WriteObject(objects[i], file, Py_PRINT_RAW); if (err) { Py_DECREF(file); return NULL; } } if (end == NULL) { err = PyFile_WriteString("\n", file); } else { err = PyFile_WriteObject(end, file, Py_PRINT_RAW); } if (err) { Py_DECREF(file); return NULL; } if (flush) { if (_PyFile_Flush(file) < 0) { Py_DECREF(file); return NULL; } } Py_DECREF(file); Py_RETURN_NONE; } ``` ::: 可以想像沒有函式的世界每個地方要print出東西的話,就會需要重新撰寫上面那串程式(實際是這個是c語言) 因此函式的主要作用:避免撰寫重複的程式碼、讓程式的主邏輯看起來更簡潔 那麼python裡面要如何定義函式? ```python def max(lhs, rhs): return lhs if lhs > rhs else rhs ``` 上面這個函式的主要目的是根據輸入的兩個數回傳哪個比較大 解釋一下每個部份的意思 ```python def 代表define 在python中代表定義函式的意思 定義一個函式的方式就像是上面的程式一樣 整體的架構像是下面這樣 def 函式名字(參數1, 參數2, 參數3, ..., 參數n): <-像是迴圈一樣接一個冒號代表函式的開始 #像是一般程式一樣開始撰寫程式邏輯 要記得縮排 return 的意思是這個函式的輸出,像是數學中f(x)的值一樣,會有輸出 所以外面呼叫這個函式並且想要得到他的值的話就會這樣寫 value = max(1, 2) # value = 2 # 這裡是用一個變數叫做value來存max(1, 2)這個函式執行結果 # 也就是return 後的值 當然你也可以完全不回傳值(完全不寫return)編譯器會自動在最後加上return ``` 練習: 寫一個函式判斷一個數是否在陣列裡面 :::spoiler ```python= def is_contains(list, target): for num in list: if num == target: return True return False list = [2,5,5,2,1] print(is_contains(list, 2)) ``` ::: ## 模組 在上面的程式碼我們展示了max和is_contains兩個函式,如果我們想要在另一個.py檔中也用這兩個函式的話要怎麼辦? ##### 使用import關鍵字 我們將剛剛定義的max和is_contains兩個函式放在一個.py檔案裡面,並且叫做utils .py 此時我們再定義一個main.py檔案,裡面即可透過import utils將utils.py中的函式等等都import進來 ```python= def max(lhs, rhs): return lhs if lhs > rhs else rhs def is_contains(list, target): for num in list: if num == target: return True return False ``` 這裡有個細節是如果想要呼叫該檔案的函式的話都會需要先加上utils然後才是該函式的名字 python會有這個設計的原因是防止有同名函式撞名,進而產生錯誤 以下是import的其他寫法 ```python= from utils import max # 要用的時候直接用max而不是utils.max # from會將namespace的功能給去掉 print(max(1, 2)) # output: 2 ``` ```python= from utils import max as mx # as 是用來取別名 print(mx(1, 2)) # 所以也可以這樣寫 # 因為沒有使用到from所以每次呼叫都還要加上utils import utils as ut print(ut.max(1, 2)) # output: 2 ``` :::danger 非常不建議!!! 大招:from utils import * ::: 上面的大招會做的事相當於直接把所有utils.py的東西都輸入到這個檔案中,如果好巧不巧現在的檔案以及utils.py都有同名的函式,就會亂掉 ```python= from utils import * def is_contains(list, target): return "Just blank here" list = [2,5,7,2,1] print(is_contains(list, 2)) ``` 猜猜這個is_contains是utils.py裡面的還是上面定義的? :::spoiler 答:會輸出Just blank here,也就是上面定義的 ::: 這個案例還算是比較簡單的,因為只有兩個檔案,如果有多個檔案的話,那可以想像出來肯定非常亂 實際上模組的import也會因為python的檔案架構有所不同,像是如果透過資料夾把各個python檔案都放在不同的資料夾內的話,就會需要比較特殊的東西將其轉化成模組,這個教學就先不講 ## 練習 ### 第一題 設計一個程式可以log現在程式的狀況,這個log需要可以印出這個訊息的輕重緩急、訊息的時間、訊息的內容 這個程式的log只要設計成print就好 :::info 註:log就是程式的日誌,在撰寫程式的時候可以在多個地方進行log,之後想要看看程式的狀態時,就只需要去看log日誌就好,而log又可以分為好幾個等級,像是DEBUG、INFO、WARNING、ERROR、CRITICAL等等,這些都是可以自己定義的 ::: :::info 取得現在時間方式: ```python= import datetime now = datetime.datetime.now() ``` ::: :::spoiler ```python= import datetime def log(level, message): now = datetime.datetime.now() print(f"[{level.upper()}] {now} - {message}") ``` 其實還有更好的寫法,但這裡先簡單寫就好 ::: ### 第二題 將剛剛的函式放在utils.py裡面,之後透過其他自己定義的程式去呼叫他 :::spoiler ```python= # main.py import utils a = 5 utils.log("error", a) ``` :::