# Python 教學講義 ###### tags: `tutor` `python` 本文所有內容與資料皆由本人蒐集與撰寫,轉載請註明出處。 - [Python 練習題](https://hackmd.io/UG_GPvJFRs2NUQk2r-3Mlg?view) - 參考[官方中文教學文件](https://docs.python.org/zh-tw/3/tutorial/index.html) ## Overview 程式語言分為高階語言、組合語言、機器語言等等,Python屬於高階語言的一種。機器語言與組合語言直接控制電腦硬體,但難以閱讀與開發;高階語言易於閱讀與開發,但需要「翻譯」給電腦聽。 > 來自 Python 官方網站的介紹: > Python 是一種易學、功能強大的程式語言。它有高效能的高階資料結構,也有簡單但有效的方法去實現物件導向程式設計。Python 優雅的語法和動態型別,結合其直譯特性,使它成為眾多領域和大多數平臺上,撰寫腳本和快速開發應用程式的理想語言。 常見應用:網站開發、資料分析、機器學習、遊戲開發、網路爬蟲... 其他語言:C、C++、R、Java、JavaScript、SQL、Go、Ruby...... ### 學習地圖 以臺大資工系必修為例: ![](https://i.imgur.com/Y2AdRO3.png =80%x) 以臺大資管系必修為例: ![](https://i.imgur.com/aC9lfrs.png =70%x) ## Python 入門 ### 環境建置 - 互動模式 - Open Terminal(終端機) and input 'python' - Execute each line directly - If Python is not installed, go to [Python official website](https://www.python.org/downloads/) ``` >>> 1 + 2 3 ``` - 腳本模式 - Need interpreter(直譯器) to help 'translate' - Execute the whole file or block at once - VSCode 示範 - .py 檔 與 .ipynb 檔 ```python= for i in range(3): print(i) print("The loop ends.") ``` ``` Output: 0 1 2 The loop ends. ``` - [VSCode 安裝教學](https://www.citerp.com.tw/citwp2/2021/12/22/vs-code_python_01/) > 補充:[使用 Anaconda 來建置開發環境](https://medium.com/ccclub/ccclub-python-for-beginners-tutorial-c23859d2bde4) ### 基礎語法(Basic Syntax) - Our first program: `print("Hello World!")` - `print()` 是一個函數 (function) - "Hello World!" 是給予這個函數的輸入 - 此函數會幫助我們印出給定的輸入,給使用者看 #### 計算(Computation) ``` >>> # This is a comment(註解) >>> # A comment will not be executed by python >>> 1 + 2 3 >>> 3 - 1 2 >>> 5 * 2 10 >>> 5 ** 2 # 5 的 2 次方 25 >>> 8 / 5 # 8 除以 5(回傳小數) 1.6 >>> 8 // 5 # 8 除以 5 的商 1 >>> 8 % 5 # 8 除以 5 的餘數(取 mod) 3 >>> (50 - 5 * 6) / 4 5.0 ``` #### 變數(Variable) - 我們會需要變數來存放數值運算的結果,使用 `=` 可以將數值指派給變數,可以參考 [基本命名規則](https://ithelp.ithome.com.tw/articles/10217188) - 若重複指派給相同名稱的變數,原本的值會被覆蓋掉! - `a = 10` 意為指派 10 給 a(右邊的值丟給左邊的容器) - `a == 10` 意為比較 a 是否等於 10(為邏輯判斷式) ``` >>> width = 20 >>> height = 5 * 9 >>> width * height 900 ``` - 讀取變數 ``` >>> var = input() 3 # 使用者自行輸入 >>> print(var) 3 # 電腦將 var 的值印出 ``` > 進階:[Python 下劃線的意義](https://zhuanlan.zhihu.com/p/36173202) #### 資料類別(Data Type) - 在宣告變數時,Python 自動幫我們決定資料類別 - 其他語言(如 C++) 可能需要做類別宣告:`int a = 1` - 常見的基礎資料類別如下: - 整數 integer - `3` - 小數(浮點數) float - `3.0` - 字母 character - `'a'` - 字串 string - `"This is a string"` - 布林值 boolean - `True` (Non-zero) / `False` (Zero) > 補充:String 是由 Character 組成的陣列,其他語言有可能會將 String 與 Character 當作兩種資料類別,但在 Python 中沒有 Character 的概念,因此長度為一的字母在 Python 中也會被當成字串來做處理。 #### 型別轉換(Casting) ``` >>> str(3) '3' >>> int("3") 3 >>> float(3) 3.0 >>> float("string") Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: could not convert string to float: 'string' >>> type(3) # 檢查資料類別 <class 'int'> ``` #### 指派 & 自我指派 (Assignment & Self-assignment) ```python= a = 10 print(a) a = a + 2 # 把 a + 2 指派給 a print(a) a += 2 # a 自己等於自己 + 2 print(a) ``` ``` Output: 10 12 14 ``` > 相同的還有 `-=` `/=` `*=` `//=` `**=` `%=` ... #### 比較/邏輯運算元(Comparison & Logical Operators) - < / <=:小於 / 小於等於 - \> / >=:大於 / 大於等於 - == / !=:等於 / 不等於 - `and`:且 - `or`:或 - `not`:非 ### 寫程式的流程(Workflow) - Debug:在我們遇到各種 Error 時,需要去檢查程式哪裡寫錯 - 有時候是語法錯、有時候是邏輯錯... - 整體的寫程式流程: ![](https://i.imgur.com/kDs6oep.png =50%x) ### 電腦架構 非常簡易版的架構圖如下: - Input:鍵盤、滑鼠、觸控螢幕、麥克風等等 - Output:螢幕、喇叭、印表機等等 - Storage:硬碟、光碟機等等 - CPU(中央處理器):負責電腦的大部分運算 - Memory:電腦內暫存的記憶體空間 > 補充 - GPU(顯示卡):負責遊戲、3D繪圖、機器學習等等運算 > 若對這個有興趣的話,可以去查「計算機結構」或修相關課程 ![](https://i.imgur.com/pmeyrcm.png =40%x) ### 二進位制 - 電腦使用二進位制來儲存數值,簡易的對照如下圖 > 若對這個有興趣的話,可以去查「數位邏輯」、「電路學」或修相關課程 ![](https://i.imgur.com/o0VuIPk.png =30%x) ### 排版方式(Formatting) 一些排版準則如下: - 通常在運算元前後會空白 - 在每個區段的 code 前後會空行,才不會全部擠在一起不好分辨 - 變數命名要有意義,讓別人也看得懂你在寫什麼 - 加入註解提高程式易讀性,並說明撰寫邏輯、使用方法等等 寫程式除了讓他可以執行以外,讓別人看懂也是一件很重要的事情。 當未來需要進行多人的大型開發時,程式碼的簡潔易懂可以大大加快開發協作時間。 想了解更多可以搜尋 Google coding style 或 SOLID 原則。 ## Python 基礎 (1) ### 條件判斷(Conditionals) ```python= price = int(input()) if price < 100: print("It's cheap.") elif price >= 100 and price < 200: print("It's okay.") else: print("It's too expensive!") ``` - 若...則... (`if`) ,若...則... (`elif`) ,若以上皆非則... (`else`) - `if` 跟 `else` 是一組的,後面要放條件判斷 or 布林值,`elif` 可有可無 - 底下的指令則需縮排,讓 Python 知道哪些是條件成立需要執行的 - 裡面還可以再包 `if-else` (巢狀條件判斷) ```python= if ... if ... ... else ... else ... if ... ... else ... ``` - 另一種寫法:A `if` condition `else` B(若 condition 為真,則執行 A ,否則執行 B) > 補充:在 Python 中,縮排是很重要的,Python 會用縮排來判斷每行程式碼的所在層級 > 縮排不同,執行起來的結果有可能完全不同! ### 迴圈(Iterations) - 我們很常需要電腦幫我們做重複的工作 - 迴圈就可以幫我們達到此目的 - 迴圈基本上分為兩種語法: `while` 跟 `for` - `while` 可以想成不斷執行的 `if`,直到條件不再成立為止 - 要小心「無窮迴圈」的發生 - `for` 則是針對清單內的元素,每個都執行一次所有指令 - 常搭配 range() 或是清單一起使用 ```python= i = 0 while i < 3: print(i) i += 1 print("The loop ends.") ``` ```python= for i in [0,1,2]: # Or for i in range(3): print(i) print("The loop ends.") ``` ``` Output: 0 1 2 The loop ends. ``` > 備註:`i` is called 'Loop Counter' in above examples > For 迴圈會自動更新 Loop Counter,While 迴圈則不會 #### 無窮迴圈(Infinite Loop) - 當一個迴圈無法停止執行時,就稱為無窮迴圈 - 無窮迴圈只能透過強制停止的方式來結束!(Ctrl + C) - 示範: ```python= while 4 > 3: # A always true condidtion print('Loop') ``` #### Range() 上面的例子中有使用到 range(),而 range() 是能夠幫助我們創造一個範圍的函數,其用法為: - `range(n)` 會回傳 `[0,1,2,...n-1]` 的清單 - `range(m,n)` 會回傳 `[m,m+1,m+2,...n-1]` 的清單 - `range(m,n,k)` 會回傳 `[m,m+k,m+2k,...]` 的清單,最後一個元素不超過 n-1 一般的情況下使用第一個就好。 > 備註:回傳型態其實不完全是清單,但我們先把它當成清單用就好 > 可以透過 `list()` 將其轉為清單 #### 巢狀迴圈(Nested Loop) - 迴圈裡也可以再放迴圈,很多複雜的程式都需要用到 - 要注意各個迴圈的執行順序與邏輯,同時撰寫避免不必要的迴圈 ```python= for i in range(3): print(i) for j in range(2): print(">",j) print('=====') ``` ``` Output: 0 > 0 > 1 ===== 1 > 0 > 1 ===== 2 > 0 > 1 ===== ``` #### 迴圈特殊處理 - break & continue - `break`:跳出迴圈外,直接結束迴圈執行 - `continue`:跳過後面的指令,直接結束此次迴圈,並進行下一次迴圈 - 用以控制迴圈,給予迴圈多個「出口」 - 若放在多重迴圈內,只會跳出一層迴圈 - 要小心不要寫出沒有意義的 `break` & `continue`! ```python= i = 0 while (i < 10): i += 1 if i == 2: continue if i == 6: break print(i) ``` ``` Output: 1 3 4 5 ``` ### 字串處理 - 字串基本上可視為字母陣列 (Array),基本操作如下: ``` >>> s = 'String' >>> print(type(s)) <class 'str'> >>> print(s[0]) 'S' >>> print(s[-1]) 'g' >>> print(len(s)) 6 >>> print(s+s) 'StringString' >>> print(s,s) String String >>> print(s+"&"+s) String&String >>> print(s*3) StringStringString >>> print(s.replace('Str','do')) doing >>> print(s.find('ing')) 3 >>> print(s.upper()) STRING >>> print(s.lower()) string >>> print('t' in s) True ``` ### 清單(List) - 清單是 Python 最常用、也最好用的資料類別,具順序性 - 甚麼東西都可以裝,裝的東西也可以不同,也可以用清單包清單 - 想成一個百寶袋,甚麼都可以塞,再拿出來 - 32位python的儲存上限是536870912 個元素 - 64位python的儲存上限是1152921504606846975 個元素 - 前面提到的字母陣列其實概念跟清單很像 ``` >>> l = [1, 1.0, 10, "test"] >>> print(l[0]) 1 >>> print(l[2]) 10 >>> l[2] = 100 >>> print(l[2]) 100 >>> print(l[-1]) "test" >>> print(len(l)) 4 >>> l.append("123") >>> print(l) [1, 1.0, 100, "test", "123"] >>> l.pop() "123" >>> print(l) [1, 1.0, 100, "test"] >>> print(l + l) [1, 1.0, 100, "test", 1, 1.0, 100, "test"] >>> print(l * 3) [1, 1.0, 100, "test", 1, 1.0, 100, "test", 1, 1.0, 100, "test"] ``` - Traverse a list: ```python= l = [1,2,3,4,5] for i in l: # or for i in range(len(l)) print(i * 2) ``` ``` Output: 2 4 6 8 10 ``` - 常見操作(Common Operations,供參考): | Method | Usage | | ----- | ----- | | list.append(x) | Add element x to end of list. | | list.sort() | Sort (order) the list. A comparison function may be passed as a parameter. | | list.reverse() | Reverse the list. | | list.index(x) | Returns index of first occurrence of x. | | list.insert(i, x) | Insert x into list at index i. | | list.count(x) | Returns the number of occurrences of x in list. | | list.remove(x) | Deletes the first occurrence of x in list. | | list.pop(i) | Deletes the ith element of the list and returns its value. | #### List Copying - 在複製 List 時,要特別留意以下狀況,並非正確的 List 複製方法: ```python= # Wrong Copy (Reference Copy Only) aList = [1, 2, 3] anotherList = aList anotherList[0] = 5 print(aList) # Check their address print(id(aList), id(anotherList)) ``` ``` Output: [5, 2, 3] 1805364504896 1805364504896 ``` - 當我們修改 `anotherList` 時,原本的 `aList` 也一同被修改 - 主要是因為 List 儲存的是記憶體參照(或是說 List 是可變物件,後面會提到),第三行做的事情僅僅是將參照傳給另一個變數,因此也可以發現他們的記憶體其實是相同的 ##### How to copy a list correctly? 有以下幾種方式可以正確地複製 List: ```python= # Correct Copy aList = [1, 2, 3] # Three different ways to copy a list (Shallow) anotherList = list(aList) anotherList = aList[:] anotherList = aList.copy() anotherList[0] = 5 print(aList) # Check their address print(id(aList), id(anotherList)) ``` ``` Output: [1, 2, 3] 1805364505024 1805364643392 ``` > 補充:此處使用的稱為「Shallow Copy」,僅複製容器中元素的地址 > 若連容器中的元素本身都想完全複製,需要使用「Deep Copy」 > 延伸閱讀: [Python - 淺複製(shallow copy)與深複製(deep copy)](https://ithelp.ithome.com.tw/articles/10221255) ### CSV(Comma-separated value)檔案 - CSV 是常見的儲存資料格式 - 簡潔、統一、格式化、方便處理 ``` QuotaAmount,StartDate,OwnerName,Username 150000,2016-01-01,Chris Riley,trailhead9.ub20k5i9t8ou@example.com 150000,2016-02-01,Chris Riley,trailhead9.ub20k5i9t8ou@example.com 150000,2016-03-01,Chris Riley,trailhead9.ub20k5i9t8ou@example.com 150000,2016-01-01,Harold Campbell,trailhead14.jibpbwvuy67t@example.com 150000,2016-02-01,Harold Campbell,trailhead14.jibpbwvuy67t@example.com 150000,2016-03-01,Harold Campbell,trailhead14.jibpbwvuy67t@example.com 150000,2016-01-01,Jessica Nichols,trailhead19.d1fxj2goytkp@example.com 150000,2016-02-01,Jessica Nichols,trailhead19.d1fxj2goytkp@example.com 150000,2016-03-01,Jessica Nichols,trailhead19.d1fxj2goytkp@example.com ``` 結合前面的字串與清單處理方式,我們可以輕鬆的處理 CSV file 中的每一行資料: ``` >>> line = 'amount,date,owner,user' >>> data = line.split(',') >>> print(data) ['amount', 'date', 'owner', 'user'] >>> print(data[0]) amount ``` 那如何處理整個 CSV file?使用檔案處理相關函數(之後再講): ```python= result = [] with open('file.csv') as f: data = f.read() lines = data.split('\n') for line in lines: result.append(line.split(',')) print(result) ``` ``` Output: [[QuotaAmount,StartDate,OwnerName,Username], [150000,2016-01-01,Chris Riley,trailhead9.ub20k5i9t8ou@example.com], [150000,2016-02-01,Chris Riley,trailhead9.ub20k5i9t8ou@example.com], ...] ``` ## Python 基礎 (2) ### 函數(Function) 我們以前寫 print('Hello') 時,其實就是在呼叫函數,這個函數會幫我們把我們傳入的 'Hello' 印出來。其他像是 range()、type()、input() 等也都是函數,各有不同的用途。 我們也可以透過特定語法來定義自己的函數,透過函數可以幫我們達成「模組化」,省去重複的 code 同時提供更多彈性來執行類似的動作。 一個函數包含名稱、本體、輸入(Input)與輸出(Output),後兩者又叫做參數(Parameters)與回傳值(Return Values)。有時我們也會在函數最一開始的地方加入註解,來說明函數的使用方式以及參數 / 回傳值類型。 ![image](https://hackmd.io/_uploads/ByAuqF8kR.png =80%x) 以下圖為例,輸入是蘋果,輸出是切半的蘋果,函數 `h` 的作用是把蘋果切半。 ![image](https://hackmd.io/_uploads/HJDXscDyA.png) #### 名稱 - 用關鍵字 `def` 來宣告函數,名稱接在 `def` 後面 - 名稱通常會取與函數作用相關,便於使用者理解函數功能 - 使用函數時,用其名稱來呼叫函數 #### 本體 - 把要執行的程式碼包在函數本體中 - 有時會在本體前面加上註解,用以說明函數功能 - 說明最好包含:輸入、輸出、作用 - 因為函數沒有限制變數的類別,所以最好在說明中講清楚 #### 輸入(參數) - **參數的作用是提供資料給函數操作** - 函數的參數可以自行命名(如下例的 n) - 可以傳入多個參數,用逗號隔開 - 可以給定預設值(如下例的 n = 5) #### 輸出(回傳值) - **回傳值的作用是把結果回傳給使用函數的人** - 使用 `return` 來控制函數的結束點,並將回傳值放在後面 - 若沒有 `return` 則會自動在最後加上 `return None` - 可放回傳多個結果,用逗號隔開 - 函數結束後會回到主程式,繼續執行後面的程式 以下是一個在 python 中的實際例子,輸入是一個數字 `n` ,輸出是一個清單 `alist` ,函數 `get_1_to_n` 的作用是獲取 1 ~ n 的清單。 ```python= def get_1_to_n(n = 5): print('Getting a list range from 1 to',n) alist = list(range(1,n+1)) return alist x = get_1_to_n(10) # or get_1_to_n(n = 10) print('X = ',x) print('~~~~~~~~~~') y = get_1_to_n() print('Y = ',y) ``` ``` Output: Getting a list range from 1 to 10 X = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] ~~~~~~~~~~ Getting a list range from 1 to 5 Y = [1, 2, 3, 4, 5] ``` > 補充:[Python yield的用法詳解](https://medium.com/ai%E5%8F%8D%E6%96%97%E5%9F%8E/python-yield%E7%9A%84%E7%94%A8%E6%B3%95%E8%A9%B3%E8%A7%A3-%E8%BD%89%E9%8C%84-52f539b67bdf) > 補充:[參數(Parameters)與引數(Arguments)的差異](https://notfalse.net/6/arg-vs-param) #### 變數範圍(Scope of Variable) 變數依據生命週期的不同,分為全域變數與區域變數。 - 區域變數(Local Variable) - 定義在函數內的變數稱為區域變數 - 只能在函數內使用,函數結束後變數也會跟著消失 - 全域變數(Global Variable) - 定義在函數外的變數稱為全域變數 - 所有地方(包含函數內)都可以使用,直到程式結束執行才會消失 - 若函數內宣告與全域變數同名的變數,則會被當作是區域變數,對其進行的操作不影響全域變數 - 通常若我們需要拿到函數內的某個變數,我們會直接使用 `return var` ```python= scale = 3 # Global def multiply(num): return num * scale def multiply_5(num): scale = 5 # Local return num * scale print(scale) print(multiply(2)) print(multiply_5(2)) print(scale) ``` ``` Output: 3 6 10 3 ``` > 補充:在函數內修改全域變數與上一層變數的方法:[Global & Nonlocal](https://ktinglee.github.io/LearningPython100days(6)_global_and_nonlocal/) #### 可變物件(Mutable Object)與不可變物件(Immutable Object) 在 Python 中,不同資料類別又可以其性質分為可變物件與不可變物件。 | 分類 | 可變物件 | 不可變物件 | | ---- | -------- | -------- | | 說明 | 被創造出來後,其值可以被改變的物件。 | 被創造出來後,其值無法被改變的物件。 | | 舉例 | list, dict, set* | int, float, string, tuple | | 修改 | 可以,依資料類別不同有不同修改方式,修改時記憶體位置不會改變。 | 無法,只能透過重新指派的方式,此時記憶體位置亦會被重新分配。 | ```python= # Mutable Object alist = [1,2,3] alist = [4,5,6] # Okay alist[1] = 100 # Okay # Immutable Object astring = 'string' astring = 'STRING' # okay astring[1] = 'A' # TypeError: 'str' object does not support item assignment ``` 接著我們來看看記憶體位址的變化: ```python= # Let's take a look on the addresses of these objects # id() is a function help finding address of a variable # Mutable Object alist = [1,2,3] print(id(alist)) alist[1] = 100 print(id(alist)) print('=====') # Immutable Object astring = 'string' print(id(astring)) astring = 'STRING' print(id(astring)) ``` ``` Output: 1541330859072 1541330859072 ===== 1541255790064 1541259351920 ``` > 參考 [什麼是 Immutable & Mutable objects](https://www.maxlist.xyz/2021/01/26/python-immutable-mutable-objects/) > \*關於 set 可不可變其實有點[爭議](https://stackoverflow.com/questions/14193438/are-python-sets-mutable),在這裡先當作他是可變的 > #### Pass by Assignment - Example Illustration 此處我們「不會」深入講當傳參數時發生了什麼事情,因為牽扯到一些記憶體跟參照等等的概念,我們會用幾個例子來說明何謂 Python 的 Pass by Assignment。 Python 中函數依據傳入參數的類別不同,會有不同的行為。 - 當傳入參數可變物件時: - **若未經重新指派,而是在函數裡直接修改參數,則會原始變數的值也會一同被修改** - **若經重新指派,則視為全新的變數,原始變數不會被影響** - 當傳入參數為不可變物件時: - **任何對參數的操作都不影響原始變數(除非使用全域變數方式修改)** 聽起來很複雜對吧?我們直接用例子來看會比較清楚一些: ```python= def listchange(l): l[0] = 'A' alist = [1,2,3] listchange(alist) print(alist) ``` ``` Output: ['A',2,3] ``` ```python= def strchange(s): s = 'STRING' astring = 'string' strchange(astring) print(astring) ``` ``` Output: string ``` 在以上的例子中,`alist` 為可變物件,因此做為參數傳入並在函數中修改時,原始的 `alist` 也一同被修改;而 `astring` 為不可變物件,因此做為參數傳入時,我們並無法直接修改他的值,只能透過重新指派的方式給予 `'STRING'` 這個值,而原始的 `astring` 依然存放 `string` 這個值沒有改變。 我們在撰寫函數時,比較好的方式是不要直接修改原始參數的值,而是將修改後的值存放在新的變數中,並作為回傳值傳回給呼叫函數的地方,以避免混淆的狀況。 > 參考 [關於 Python 獨有的 Pass by Assignment](https://luka.tw/Python/%E5%9F%BA%E7%A4%8E%E6%95%99%E5%AD%B8/past/2021-09-21-is-python-call-by-sharing-122a4bf5a956/),以及 [英文版本(Stackoverflow)](https://stackoverflow.com/questions/986006/how-do-i-pass-a-variable-by-reference) #### 遞迴(Recursion) - An example on factorial 遞迴是一種概念,指的是「在函數在執行過程中呼叫自己」的方法。這種技術對於解決某些複雜問題特別有用,例如處理樹狀結構、遞迴搜尋、組合數學等。以下是遞迴的基本概念和特性: 1. 基礎案例(Base Case):遞迴函數必須有一個基礎案例,也就是遞迴呼叫的終止條件。當滿足這個條件時,遞迴將不再進行,從而避免無限迴圈。 2. 遞迴案例(Recursive Case):如果沒有滿足基礎案例的條件,函數就會進入遞迴案例。在這個案例中,函數會呼叫一個較小的子問題版本。 3. 問題簡化:遞迴案例通常將原始問題簡化為一個較小的子問題,直到滿足基礎案例為止。 ```python= def find_fact(n): if n == 1: # base case return 1 else: # recursive case recurse = find_fact(n-1) result = n * recurse return result ``` ![image](https://hackmd.io/_uploads/S1gBbY810.png) > 補充:[遞迴深度的上限](https://clay-atlas.com/blog/2020/09/20/python-cn-recursionerror-maximum-recursion-depth-exceeded/) 關於函數還有很多可以講:Recursion 的設計方法、Call by Reference、Call by Value...。但有些東西太進階了,我們先停在這裡,以後有機會或是遇到的時候再細講。 ### 其他常見資料結構 #### 元組(Tuple) - 與 list 類似,但是屬不可變物件 - 不同於 list 使用 `[]` ,tuple 使用 `()` - 一個元素的 tuple 須以 `(item, )` 表示 - 因屬不可變物件,故僅能以重新指派的方式修改其值,如下例: ``` >>> mytuple = (11, 22, 33) >>> saved = mytuple >>> mytuple += (44,) >>> mytuple (11, 22, 33, 44) >>> saved (11, 22, 33) ``` - zip() ```python= num = [1,2,3] char = ['a','b','c'] CHAR = ['A','B','C'] for i in zip(num,char,CHAR): print(i) ``` ``` Output: (1, 'a', 'A') (2, 'b', 'B') (3, 'c', 'C') ``` #### 字典(Dictionary) 當我們需要了解某個字的讀音時,我們會去查找字典,在其中尋找對應的讀音。這種 {字: 讀音} 的配對,在 Python 中可以透過字典來實現。 - 字典的組成包含鍵(Keys,不可變)與值(Values,可變) - 使用 Key 來尋找對應的 Value,以上述例子來說即為使用字尋找讀音 - Key 跟 Value 可以是任何資料類別,也可以不用一樣 - 字典是無序的(在 `collections` 這個 library 中有提供有序字典) - 若查找不存在的 key 則會報錯,可以使用 `in` 或 `dict.get()` 來檢查 ```python= mydict = dict() # or mydict = {} print("Here is an empty dictionary:", mydict) mydict[1] = 'one' mydict[2] = 'two' mydict[3] = 'three' print(mydict) print("2 is corresponding to:", mydict[2]) ``` ``` Output: Here is an empty dictionary: {} {1: 'one', 2: 'two', 3: 'three'} 2 is corresponding to: two ``` ```python= # Continued from last cell print(mydict.keys()) print(mydict.values()) print(mydict.items()) print(5 in mydict) print(mydict.get(5)) ``` ``` Output: dict_keys([1, 2, 3]) dict_values(['one', 'two', 'three']) dict_items([(1, 'one'), (2, 'two'), (3, 'three')]) False None ``` #### 集合(Set) - 與數學中的集合概念類似,只能儲存唯一(Unique)元素,相同元素不會重複出現 - 因為是無序,故我們不能使用 `set[0]` 的方式來取值 - 可以使用 `set.add(item)` 與 `set.remove(item)` 來新增與刪除元素 - 可以使用 `set(list)` 將 List 轉為 Set,藉此檢查清單中唯一元素個數 - 相關操作有聯集、交集、差集等等 ```python= aset = {11, 22, 33} bset = {33, 44, 55} print("Union:", aset | bset) print("Intersection:", aset & bset) print("Difference(A-B):", aset - bset) print("Difference(B-A):", bset - aset) print("Symmetric difference:", aset ^ bset) ``` ``` Output: Union: {33, 22, 55, 11, 44} Intersection: {33} Difference(A-B): {11, 22} Difference(B-A): {44, 55} Symmetric difference: {11, 44, 22, 55} ``` ![image](https://hackmd.io/_uploads/BkvMrFNM0.png) > 補充:特別注意 [運算元優先順序](https://june.monster/python-101-operators-and-priority/) ! #### 統整 | 類別 | Tuple | List | Dict | Set | | --- | --- | --- | --- | --- | | 符號 | ( ) | [ ] | { } | { } | | 可變性 | 不可變 | 可變 | 可變 | 可變 | | 順序性 | 有序 | 有序 | 無序 | 無序 | ### 檔案讀取(File I/O) 在 Python 中,很常會用到檔案相關的操作,舉凡文字檔(.txt)、CSV檔(.csv)、圖片檔(.png, .jpg...)、影片檔(.mp4, .avi...)等等,都會需要讀取、寫入檔案。這邊先以文字檔作為示範,僅簡單講解基礎操作,其他檔案如圖片、影像有些會有專門的 library 來處理。 test.txt: This is a test txt file. This is another line. - 全部讀入 ```python= file = open('test.txt','r') data = file.read() file.close() print(data) data = data.split('\n') print(data) ``` ``` Output: This is a test txt file. This is another line. ['This is a test txt file.', 'This is another line.'] ``` - 逐行讀入 ```python= file = open('test.txt','r') while True: line = file.readline() if not line: break print(line,end='') file.close() ``` ``` Output: This is a test txt file. This is another line. ``` - 寫入檔案 - 新增在原始資料後面 ```python= file = open('test.txt','a') file.write('This is a new line.') file.close() ``` test.txt: This is a test txt file. This is another line. This is a new line. - 從頭重新寫入(會覆蓋原始資料) ```python= file = open('test.txt','w') file.write('This is a new line.') file.close() ``` test.txt: This is a new line. 要記得加上 `file.close()` 來關閉檔案,以免造成潛在的 Memory Leak。有一個更好的寫法,使用 `with` 來達成,如下例: ```python= with open('test.txt','r') as file: # Do some file-related operations # The file will close automatically when this block is finished # Do other operations ``` 將檔案相關操作放在 `with` 的區塊中,而非檔案相關操作放在外面,一方面能確保檔案有被關閉,一方面也能增加可讀性。 ### 例外處理(Exception Handling) 寫程式難免會遇到 Error 的狀況,當我們不希望程式因為 Error 而停止執行並噴出一大堆錯誤訊息時,可以使用以下技巧來處理。 - `try` 必須搭配 `except` ,預設先執行 `try` 裡的程式碼 - 當 `try` 執行失敗時, `except` 裡才會被執行 - 與 `if...else...` 有異曲同工之妙,但不會因遇到錯誤而停止執行 - 有時候會造成難以 debug ,使用上要特別小心 - 另外可以使用 `raise` 來定義自己想要的 Exception ```python= x = input() if not x.isdigit(): raise Exception("Not A Integer") else: x = int(x) try: inv = 1/x print(inv) except ZeroDivisionError: print('Denominator cannot be 0!') except TypeError: print('Type is not correct!') except: print('Other Error!') ``` ``` Output (when input = 2): 0.5 Output (when input = 0): Denominator cannot be 0! Output (when input = 'A'): ... Exception: Not A Integer ``` 此處要注意的是,前兩種狀況程式可以順利結束,因為我們使用 `except` 來處理分母為0的例外;但第三種狀況 Python 會報錯,程式中斷無法繼續往下執行,因為我們 寫「當 x 不是數字時就 raise error」,Python raise error 後就會停止。 ### 斷言 (Assertion) Assertion 提供一種保護機制,確保執行到某個地方時,某樣我們預期的條件仍然成立。若不成立則會丟出 Assertion Error,可以加入自定義的訊息。 ```python= x = int(input()) assert x >= 0, 'x is not positive' print('A Positive number is:',x) ``` ``` Output (when input = 1): A Positive number is: 1 Output (when input = -1): ... AssertionError: x is not positive ``` ### Lambda Lambda 又叫做匿名函數,當我們需要快速簡潔的撰寫一個函數,但又不想幫他命名時(意即這個函數可能只會用一兩次),我們就會使用 Lambda 來幫助我們。Lambda 在使用上依然可以給予名稱,但非必要,函數內容也必須在一行內結束。 ```python= >>> print((lambda x : x ** 2)(10)) 100 >>> print((lambda x, y: x * y)(4, 5)) 20 >>> print((lambda x: x[1])([1,2,3])) 2 ``` #### 與其他函數的搭配使用 - filter() ```python= numbers = [1,10,100,1000,10000] bignums = filter((lambda x: x > 50), numbers) print(list(bignums)) ``` ``` Output: [100,1000,10000] ``` - map() ```python= numbers = [1,10,100,1000,10000] doublenums = map((lambda x: x * 2), numbers) print(list(doublenums)) ``` ``` Output: [2, 20, 200, 2000, 20000] ``` - sorted() ```python= food = [('Apple',10),('Coke',30),('Bread',5),('Candy',25)] food_sorted = sorted(food, key = lambda x: x[1]) print(food_sorted) ``` ``` Output: [('Bread', 5), ('Apple', 10), ('Candy', 25), ('Coke', 30)] ``` > 參考 [Python Lambda 應用技巧](https://www.learncodewithmike.com/2019/12/python-lambda-functions.html) ### 套件(Library) Python 強大的地方就是其眾多的使用者,讓我們在網路上有許多參考資料,以及眾多的第三方套件可供我們使用。套件其實就是別人寫好的 .py 檔案,將其整理後丟到網路上,讓我們可以透過一行簡單的 `import [package]` 就能使用。 - 安裝套件:使用 `pip install [package]` 有些套件如 `os`, `random`, `time` 等等已預先包含在 python 中,就不需再另外下載 - 載入套件 - 整個套件載入 - `import [package]`(推薦) - `from [package] import *` - 僅載入特定模組/函數 - `from [package] import [module/function]`(推薦) - `import [package].[module]` - 載入且化名 - `import [package] as [name]` - 使用套件:多以`[package].[function]`的方式,若是僅載入特定模組/函數的話,前面不須加套件名稱即可使用。以下四種方式結果皆相同,但為了方便管理我們一般會選用第一種,以便知道各個函數來自哪個套件,同時也不會不小心覆寫(Overwrite)某些函數。 ```python= import os print(os.getcwd()) ``` ```python= from os import getcwd print(getcwd()) ``` ```python= import os as O print(O.getcwd()) ``` ```python= from os import getcwd as gw print(gw()) ``` > 進階:參考 [Python 的 import 陷阱](https://medium.com/pyladies-taiwan/python-%E7%9A%84-import-%E9%99%B7%E9%98%B1-3538e74f57e3) #### 常用套件 - GUI:tkinter - 遊戲設計:pygame - 數學運算:math, random, numpy, scipy, random - 資料處理:numpy, pandas - 視覺化:matplotlib, seaborn - 機器學習:scikit-learn, pytorch, tensorflow, keras - 網站建置:flask, django - 資料庫處理:pymysql - 影像處理:cv2, PIL - 自然語言處理:nltk, jieba - 電腦操作:os, sys - 時間相關:time, datetime - 網路爬蟲:request, beautifulsoup, selenium > 參考 [Python 第三方模組](https://cflin.com/course/python/Python_07.pdf) ## Git 版本控制 Git 是一種版本控制系統,它可以追蹤軟體開發過程中的變更,幫助開發人員更有效地管理程式碼。使用 Git 有許多好處: 1. 版本控制:Git 可以幫助開發人員追蹤檔案的更改,並在需要時輕鬆地回復到先前的版本。這樣可以減少錯誤和失誤,並提高程式碼品質。 2. 合作開發:Git 可以讓多個開發人員協同工作,讓他們在同一時間在同一份程式碼上工作,並且避免不同人員之間的衝突。 3. 分支管理:Git 可以讓開發人員在不影響主分支的情況下創建和管理多個分支。這可以讓開發人員在不同的功能上工作,而不必擔心對主分支的影響。 4. 遠端存儲:Git 可以讓開發人員將代碼儲存在遠端儲存庫中,讓多個開發人員在不同地方協同工作。 Git 在許多著名的開源軟體專案中得到廣泛使用,包括Linux核心、Ruby on Rails 和 jQuery。使用 Git 的方式一般有兩種:使用指令(Command Line)或是下載 Github Desktop 使用其軟體介面(GUI),我們這邊會先介紹如何使用 Github Desktop。 Credit: The world-wide famous [ChatGPT](https://chat.openai.com/chat) 參考以下連結: - [官方說明](https://docs.github.com/zh/desktop/installing-and-configuring-github-desktop/overview/getting-started-with-github-desktop) - [從 0 到 1 的 GitHub Pages 教學手冊](/cW7RxOjzQ4eqQlZbOW9BsA) - [Git 教學 - 為你自己學 Git](https://gitbook.tw/) ## Python 進階 ### 物件導向程式設計(Object-Oriented Programming, OOP) 物件導向程式設計是軟體設計的一種方法,它把軟體分成數個「物件」來撰寫。每個物件都有自己的屬性和行為,並且可以跟其他物件互動。這樣的好處是,軟體的各部分之間彼此獨立,不但便於重複使用,也更容易理解和修改,提高軟體的可維護性和可擴展性。 物件導向程式設計是目前最流行的軟體設計方法之一,被廣泛應用於各種領域,包括網站開發、商用軟體、遊戲開發等等。常見的物件導向程式設計語言包括 Java、C++、C#、Python 等。 Credit: The world-wide famous [ChatGPT](https://chat.openai.com/chat) ### 類別(Class) - 簡介 以下我們使用一個簡單的例子來說明類別的概念:在現實生活中,有各式各樣的車子,而每台車子雖然皆不相同,但都具有共同特徵,像是有四個輪胎、都有駕駛與車牌跟廠牌、都使用汽油前進等等,這時候就很適合使用物件導向的概念為車子建造一個類別。 在以下的例子中,`Car` 是一個類別名稱,這個類別包含 `driver, engine, meter` 等等屬性(Attribute),以及 `turnOnEngine, checkEngine, drive` 等等方法(Method)。 而 `mycar` 是一個屬於 `Car` 類別的物件或變數,我們也可以建立多個屬於 `Car` 類別的物件像是 `mycar1, mycar2...`,彼此之間的屬性與函數操作互不影響。 #### 宣告類別與建構函式(Constructor) 在宣告類別時,我們使用以下語法: - `class Car` 代表這個類別的名稱,亦可使用 `class Car()` 或 `class Car(object)` - `def __init__()` 是一種特殊的類別方法,也稱為建構函式 - 一個類別只有一個建構函式,若未撰寫則預設什麼事情都不做 - 名稱必為 `__init__()`,建立此類別物件時會自動執行,不須呼叫 - 主要用途為初始化相關配置,像是車子必有車牌號碼等等 ```python= class Car: # or class Car(): / class Car(object): def __init__(self, plateID, driver): self.wheels = 4 self.plateID = plateID self.driver = driver self.engine = False self.meters = 0 self.turnOnEngine() ``` > 補充:在其他語言(如 C++)中,時常會見到解構函式(Destructor)的使用 > 與建構函式相對應,解構函式在物件被銷毀時會自動執行 > 其使用主要是為了刪除 Runtime 時動態分配的記憶體空間,以避免 Memory Leak > Python 中也有提供解構函式,但因為我們通常不會自己分配記憶體 > 所以大多狀況下不需要使用,Python 會自己幫我們刪除分配的空間 #### 屬性(Attribute)與方法(Method) - 屬性(Attribute):靜態,描述此物件的屬性 - 車子有駕駛、引擎、公里數等等 - 又稱為成員變數(Member Variable) - 其值可以是任何東西,像是 `list`、`int`、`string` 等等,甚至可以是另一個類別 - 會一直保存並且隨程式進行而更新,直到物件消滅為止 - 方法(Method):動態,對此物件執行一個動作 - 車子可以點燃引擎、檢查引擎、往前開等等 - 又稱為成員函式(Member Function) - 與一般的函式(Function)撰寫方式相同 ```python= class Car: # or class Car(): / class Car(object): def __init__(self, plateID, driver): self.wheels = 4 self.plateID = plateID self.driver = driver self.engine = False self.meters = 0 self.turnOnEngine() def turnOnEngine(self): if self.checkEngine(): self.engine = True print("Engine Started!") def checkEngine(self): return True def drive(self, distance): if self.engine: self.meters += distance print("Drive %d kilometers."%distance) else: print('Engine is not turned on.') def turnOffEngine(self): self.engine = False print("Engine has been turned off.") def whoisDriving(self): print('%s is driving the car.'%self.driver) return self.driver def getMeters(self): return self.meters ``` #### Self 你應該有注意到上面出現了很多個 `self` 這個關鍵字,這個關鍵字在類別中扮演了很重要的角色,且任何類別方法中,第一個參數一定要是 `self`,他的意思是「這個物件本身」,而因為 `self` 代表這個方法中的物件本身,所以這個位置不需要傳入任何東西,在呼叫時可以直接無視。 用以下的例子來說: ```python= class Car: ... def drive(self, distance): if self.engine: self.meters += distance print("Drive %d kilometers."%distance) else: print('Engine is not turned on.') def getMeters(self): return self.meters ``` ```python= ... print(myCar1.getMeters()) myCar1.drive(100) print(myCar1.getMeters()) print(myCar2.getMeters()) print(myCar3.getMeters()) ``` ``` Output: 100 200 1000 0 ``` 可以注意到幾個重點: - 每輛車的結果都不同,因為每輛車的 `self.meters` 都不同 - 呼叫 `mycar1.getMeters()` 時,不用傳入任何參數,但定義時卻必須定義一個參數 `self`,也就是說,類別方法的傳入參數量 + 1 = 定義參數量 - 也可以在類別方法內來呼叫其他類別方法,像是 `self.turnOnEngine()` #### 靜態變數(Static Variable) 上述例子中,有些屬性是屬於整個類別共享的,像是 `self.wheels`,所有車子都有四個輪子。此時我們可以利用靜態變數,來宣告整個類別的屬性。詳細作法如下: ```python= class Car: # or class Car(): / class Car(object): wheels = 4 def __init__(self, plateID, driver): self.plateID = plateID self.driver = driver self.engine = False self.meters = 0 self.turnOnEngine() ... ``` | 變數/屬性 | 說明 | 例子 | 語法 | | ------ | -------- | -------- | -------- | | 實體變數 | 每個物件的屬性 | 每輛車有不同的駕駛 | mycar.driver | | 類別變數 | 整個類別的屬性 | 所有車都有 4 個輪子 | Car.wheels | #### 使用方式 在完成以上的宣告後,接著我們來看使用方式: - 使用 `Car()` 來建立一個類別物件 - 使用 `myCar.drive()` 來呼叫類別方法 - 使用 `myCar.driver` 來獲取類別屬性 ```python= myCar = Car("ABC-0311","Jack") print('=====') myCar.drive(100) print('=====') driverName = myCar.driver print(driverName, "is driving the car.") ``` ``` Output: Engine Started! ===== Drive 100 kilometers. ===== Jack is driving the car. ===== ``` > 補充:在其他語言中,為了更好的管理獲取權限 > 有時候會限制類別屬性或方法的取的與使用 > 此舉可以避免類別屬性被意外的修改,像 C++ 中 `private` 與 `protected` 的使用 #### 完整程式碼 ```python= class Car: # or class Car(): / class Car(object): wheels = 4 def __init__(self, plateID, driver): self.plateID = plateID self.driver = driver self.engine = False self.meters = 0 self.turnOnEngine() def turnOnEngine(self): if self.checkEngine(): self.engine = True print("Engine Started!") def checkEngine(self): return True def drive(self, distance): if self.engine: self.meters += distance print("Drive %d kilometers."%distance) else: print('Engine is not turned on.') def turnOffEngine(self): self.engine = False print("Engine has been turned off.") def whoisDriving(self): print('%s is driving the car.'%self.driver) return self.driver def getMeters(self): return self.meters myCar = Car("ABC-0311","Jack") print('=====') myCar.drive(100) print('=====') driverName = myCar.whoisDriving() print('=====') myCar.turnOffEngine() print('=====') myCar.drive(100) print('=====') myCar.turnOnEngine() print('=====') myCar.drive(100) print('=====') meter = myCar.getMeters() print("Meters:",meter) ``` ``` Output: Engine Started! ===== Drive 100 kilometers. ===== Jack is driving the car. ===== Engine has been turned off. ===== Engine is not turned on. ===== Engine Started! ===== Drive 100 kilometers. ===== Meters: 200 ``` ### OOP 三大精隨 - 封裝、繼承、多型(補充) #### 封裝(Encapsulation) 將物件的內部狀態和行為隱藏在物件內部,只公開必要的方法給外界使用。封裝可以保護物件免於外界的非法存取,並且讓物件更容易維護和修改。 ```python= class Animal: name = '' __private = '' # This cannot be accessed from the outside def __init__(self, name): self.name = name self.__private = '' # This cannot be accessed from the outside ``` #### 繼承(Inheritance) 子類別可以繼承父類別的屬性和方法,並且可以擴展或覆寫父類別的行為。繼承可以提高程式碼重複使用性,並且可以讓類別之間建立階層關係,方便對類別進行分類和管理。 ```python= class Animal: name = '' def __init__(self, name): self.name = name def walk(self): print('walking') def eat(self): print('eating') class Dog(Animal): def __init__(self, name): super().__init__(name) A = Dog('A') A.walk() A.eat() print(A.name) ``` ``` Output: walking eating A ``` #### 多型(Polymorphism) 同樣的方法名稱可以在不同的類別中有不同的實現方式,這稱為多型。多型可以讓程式碼更加靈活,並且可以讓不同的物件對相同的方法有不同的行為。多型可以通過繼承和介面實現,是物件導向設計中非常重要的概念。 ```python= class Animal: name = '' def __init__(self, name): self.name = name def walk(self): print('walking') def eat(self): print('eating') class Dog(Animal): def __init__(self, name): super().__init__(name) def walk(self): print('{0} is using foot to walk'.format(self.name)) def eat(self): print('{0} is eating bone'.format(self.name)) class Duck(Animal): def __init__(self, name): super().__init__(name) def walk(self): print('{0} is using two feet to walk'.format(self.name)) def eat(self): print('{0} is eating worm'.format(self.name)) A = Dog('A') B = Duck('B') A.eat() B.eat() ``` ``` Output: A is eating bone B is eating worm ``` > Code Source: [搞懂Python的OOP](https://ithelp.ithome.com.tw/articles/10200623) ## 小結 到這裡為止你已經學完絕大部分常用的 Python 語法了,簡單開發所需的語法基本上不太會超過本篇教學的範圍。然而,資訊工程的領域極其廣大,目前碰到的還僅止於皮毛,若有興趣可以繼續鑽研資料結構、演算法等等課題,也可以透過題目或專案來練習自己的 Coding 能力。另外,網路上有很多相關資訊或教學,透過網路自我學習、不斷成長,也是件很重要的事情,加油!