## 資料結構
資料結構是什麼?
答:實際上就是幫助你有效率去存取、修改、新增、整理資料的方法
想像以下場景:
今天做了一個可以自動批改學生成績的程式,你會需要以下步驟來進行
1. 從數位學苑中獲取全部學生的考卷
2. 讀取每個考卷,根據答案進行批改
3. 將每位學生的成績傳回去到數位學苑
解釋一下每個步驟理論上會用到甚麼
1. 獲取考卷且儲存:爬蟲獲取(之後會教)、List儲存
2. 批改:List存取
3. 上傳回去:爬蟲上傳、List存取
這裡可以看到List的使用場景
就是存放同性質的資料到一個地方

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)
```
:::