# 20240726 Python - 今日內容 - venv - 位置引數 vs 關鍵字引數 - / vs * - F 字串 - tuple vs list - 串列推導式 - [ 結果 for 個體 in 集合 if 條件 ] - LEGB, Local, Enclosing, Global, Built-in - global vs nonlocal ## pyenv vs venv pyenv - 讓你可以更容易切換多個Python版本 venv - 建立虛擬環境 (window系統中使用`PowerShell 7`) - 在終端機中輸入的指令和步驟: 1. 建立虛擬環境: python -m venv .venv => 其中的.venv可以自行定義名稱,但是`按照慣例`會寫成`.venv` 2. 切換目錄: cd .\\.venv\Script\ => 切換目錄到.venv內Scripts資料夾 3. 進入虛擬環境: .\Activate.ps1 => 命令列最前面會多一個Prompt (.venv),表示`進入`虛擬環境 ![image](https://hackmd.io/_uploads/SyEWPlbKR.png) 4. 退出虛擬環境: deactivate => 命令列最前面的Pormpt (.venv)會消失不見,表示`退出`虛擬環境 ![image](https://hackmd.io/_uploads/ryj9ueWF0.png) - 如何將環境目前pip安裝的所有套件資訊記錄到 requirements.txt 檔案中 - pip freeze > requirements.txt ```py // requirements.txt asgiref==3.8.1 certifi==2024.7.4 charset-normalizer==3.3.2 Django==5.0.7 idna==3.7 requests==2.32.3 sqlparse==0.5.1 tzdata==2024.1 urllib3==2.2.2 ``` - 如何使用存在的套件清單 requirements.txt 安裝所有套件 - 執行步驟 ```py 1. 刪除原本.venv 2. 建立一個新的虛擬環境 python -m venv .venv 3. pip install -r requirements.txt (-r, --requirement <file>) ``` - pyproject.toml 介紹 - 不是poetry套件管理工具專屬 ``` pyproject.toml 是 "PEP 518" 所提出的新標準, 原意是作為套件打包設定的標準格式,後來又有了 "PEP 621", 將其擴充定性為 Python 生態系工具的共同設定檔標準, 現在已經被愈來愈多套件所支援 ``` ## terminal vs shell - 人 -> 終端設備 -> 主機 - terminal 終端機 - terminal -> 打字 -> shell -> OS -> ==Yes== -> shell -> terminal - terminal -> 打字 -> shell -> OS -> ==No== -> Command not found - shell 殼 - 作進出翻譯的事情 ## Python 語法 - 輸出 ```py print("hi") ``` - 判斷式 ```py if True: <== T要大寫 print("hi") ``` - 函數 ```py def hi(a, b, c): pass <== 不做任何事情,保持結構完整性 ``` - 印出None ```py def hi(a, b, c): pass a = hi(1, 2, 3) print(a) ``` ```py 執行python檔案方法 (.venv) PS D:\Project\5xcampus\Python\20240726_python> python .\hi.py ``` ## 參數 vs 引數 引數 argument:傳入函式的值。 參數 parameter:函數對可接受引數的定義。 ## 型別註記 - 註記為整數型別,給予一個整數 ```py a: int = 123 ``` - 註記為字串型別,給予一個字串 ```py b: str = "abc" ``` - 註記函數參數和返回值型別 ```py def greet(name: str) -> str: return f"Hello, {name}!" def add(a: int, b: int) -> int: return a + b ``` - Python型別註記檢查工具 mypy https://github.com/python/mypy 安裝 mypy: ```py pip install mypy ``` hi.py內容 ```py def hi(a: int, b: str, c: bool): print(a, b, c) hi(1, 2, 3) ``` 運行 mypy 進行類型檢查: ```py mypy hi.py ``` [執行檢查結果] 發生錯誤原因: hi( )的第二和第三個參數分別註記為str和bool,但是引數給的都是int,所以會發生錯誤 ![image](https://hackmd.io/_uploads/Byq227ZF0.png) 執行成功結果 ```py def hi(a: int, b: int, c: int): print(a, b, c) hi(1, 2, 3) ``` ![image](https://hackmd.io/_uploads/B1HRT7WYC.png) - 內建型別要注意不要用到 (str int list...等) ## 位置引數 vs 關鍵字引數 - 位置引數 positional arguments ```py # 函式有兩個參數,依傳入的引數值順序來決定結果 def minus(x, y): return x - y print(minus(2, 1)) # 1 print(minus(1, 2)) # -1 ``` - 關鍵字引數 Keyword Argument ```py # 傳入的引數值前有加上參數名稱,所以函式會依引數名稱來取用值,傳入順序不影響結果 def minus(x, y): return x - y print(minus(x=2, y=1)) # 1 print(minus(y=1, x=2)) # 1 ``` - 混合使用位置引數和關鍵字引數,==位置引數==要在==關鍵字引數==的==前面== - ==/== "前面"的所有參數,一定只能使用`位置引數`方式帶入 - ==*== "後面"的所有參數,一定只能使用`關鍵字引數`方式帶入 - ==/== 和 ==*== 中間沒有規範到的,可以使用兩種引數方式帶入 ```py def hi(a, b, /, c, *, d, e): print(a, b, c, d, e) hi(1, 2, 3, e=5, d=4) # 1 2 3 4 5 ``` ## F 字串 (格式化字串) - 雷同於JavaScript中的backtick ( `${number}` ) ```py my_money = 1000000 print(f"{my_money:,}") # 印出 1,000,000 可當作千字撇節 print(f"{my_money:.2f}") # 印出 1000000.00 設定小數點兩位 print(f"{my_money:,.2f}") # 印出 1,000,000.00 混合使用 print(f"{number:20}") # 印出 1000000 字串左邊補上空白 print(f"{number:<20}") # 印出 1000000 靠左對齊,字串右邊補上空白 print(f"{number:>20}") # 印出 1000000 靠右對齊,字串左邊補上空白 print(f"{number:^20}") # 印出 1000000 置中對齊字,兩邊字串補上空白 ``` ![image](https://hackmd.io/_uploads/Hkpe1SWFC.png) - 時間補 0 範例 ```py hour = 1 mins = 1 seconds = 30 # 2前面0表示會預留兩位數,小於10可以在2前面補0 print(f"{hour:02}:{mins:02}:{seconds:02}") # 01:01:30 ``` ## 為何Python不叫 `陣列` 要叫 `串列`? - 因為串列中在不同結構可以放入不同型態 - 一般程式語言陣列,放的是固定型態,空間大小都一樣 - Python串列,放的是不同型態資料,可以是int, str, function.. (可以放入函數的原因,是因為函數是一等公民可以被當作對象一樣被丟來丟去) - 等到真正需要用的時候才會開始執行 ## tuple vs list - tuple: 元組、數組 - (1, 2, 3) - list: 串列 - [1, 2, 3] - 兩者主要差異: - 內容`不會變動`,要以==tuple==來寫 - 內容`會變動`,要以==list==來寫 - 一個元素的tuple表示方式,後面必須要加上一個逗號 - 需要加逗號 ![image](https://hackmd.io/_uploads/SkelXxftR.png) - 無加上逗號會無法判斷為tuple,會被視為元素本身的型別 ![image](https://hackmd.io/_uploads/S1pq7gzK0.png) ## 串列推導式 (List Comprehension) - 用來做資料轉換 [情境一] ```py numbers = [1, 2, 3, 4, 5] # [2, 4, 6, 8, 10] # [ 結果 for 個體 in 集合 ] result = [ n * 2 for n in numbers ] print(result) ``` [情境二] ```py numbers = [1, 2, 3, 4, 5] # 1, 3, 5, 7, 9 # [ 結果 for 個體 in 集合 ] result = [2 * n - 1 for n in numbers] print(result) ``` [情境說明] - 2 * n -1 => 是一求得奇數的公式 [情境三] 使用zip()函數 - zip( )是一個lazy的設計,表示用到的時候才展開,否則只是一個`物件` 不占多的空間 ```py # zip() nn = [1, 2, 3, 4, 5] mm = [2, 4, 6, 8, 10] result = [] result = [x for x in zip(nn, mm)] print(result) ``` [情境說明] 1. zip(nn, mm) 會回傳一個"疊代器(iterator)",是`未展開形式` ![image](https://hackmd.io/_uploads/Hk9eKxMYR.png) ※ 何謂疊代器(iterator): 就是一個物件,可以對這個物件的內容進行遍訪、輪巡的對象 2. 利用list()方法可以將zip()回傳的疊代器`展開`,並且把所有元素(元組)儲存在一列表中 ![image](https://hackmd.io/_uploads/SJh2YeMKA.png) [情境四] ```py # zip() nn = [1, 2, 3, 4, 5] mm = [2, 4, 6, 8, 10] result = [] result = [a * b for (a, b) in zip(nn, mm)] print(result) ``` [情境說明] 1. `for a, b in zip(nn, mm)` 因為zip回傳的疊代器內容是成對,所以可以先使用for..in..方式取得個體元素(1, 2) 2. 再以`解構概念`直接取得成對元素內的兩個值a=1, b=2 3. 最後的結果會拿a和b進行相乘 [情境五] ```py Q: 要如何取得list中tuple內的元素? a = [(1, 2), (3, 4), (5, 6)] A: 1. 先使用 a[1] 方式取得list的個體元素(3, 4) 2. 再使用 a[1][1] 方式取得個體元素中的"第1個索引值"的內容值 4 ``` ![image](https://hackmd.io/_uploads/S1KkWzMYC.png) [情境六] ```py # nn = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] nn = range(1, 11) # 這裡還不會展開 print(nn) # range(1, 11) result = [] for n in nn: if n % 2 == 0:  #求得奇數 result.append(n) #算得的奇數值存入result print(result) ``` [情境七] 同情境六題目使用串列推導公式 ```py #[ 結果 for 個體 in 集合 if 條件 ] result = [ n for n in range(1, 11) if n % 2 == 0] print(result) ``` ## 型別查詢方法 - 使用 type() ![image](https://hackmd.io/_uploads/H1vhWfGtR.png) ## Python Unpacking 開箱技巧 (多重指定,解構方式) - 串列 list ![image](https://hackmd.io/_uploads/BkLJQGft0.png) - 元組 tuple ![image](https://hackmd.io/_uploads/H1DIQfGtC.png) ## 陣列 Array  ```py # 從Python內建array模組中載入array類別 from array import array # 建立一個整數類別的陣列,'i'表示為有號整數 my_array = array('i', [1, 2, 3, 4, 5]) print(my_array) # array('i', [1, 2, 3, 4, 5]) # 建立一個整數類別的陣列,'d'表示為雙精度浮點數 my_array = array('d', [1, 2, 3, 4, 5]) print(my_array) # array('d', [1.0, 2.0, 3.0, 4.0, 5.0]) ``` ## 作用域 LEGB (Local, Enclosing, Global, Built-in) - 會先找Local的區域範圍 => Local(內層函數內的範圍) - 找不到會往上層的範圍去找 => Enclosing(外層函數到內層函數之間的範圍) - 最後都找不到才會找到最外層 => Global(外層函數以外的範圍) - 以上三個層級都找不到才會去找Python內建函數 [情境一] ```py a = 1 # 這裡是指定 def hi(): print(a) <=== 因為在hi()的範圍內沒有a,所以會往外找 hi() # 印出 1 (會印出外面的a) ``` [情境二] ```py a = 1 # 這裡是指定 def hi(): a = 9 print(a) <=== 在hi()範圍內有找到a,所以會印出範圍內的a hi() # 印出 9 ``` [情境三] ```py a = 1 # 這裡是指定 def hi(): # 第一個"a"是表示宣告一個a變數,在建立期的時候會變成TDZ # 接下來a要先+1才會賦值給第一個"a"變數,所以在a尚未賦值之前就先進行+1的動作, # 這時候就會發生錯誤 a = a + 1 print(a) hi() ``` ![image](https://hackmd.io/_uploads/SJ8FI7fKR.png) [情境四] 使用global關鍵字 ```py a = 1 # 這裡是指定 def hi(): # 從現在開始你指定的a是外面的a,a就變成全域變數,就不是宣告 global a <==== a = 1 a = 2 <==== 這裡的a會被指定為2 print(a) hi() # 2 print(a) # 2 ``` [情境五] 使用nonlocal關鍵字 ```py a = 0 def hi(): a = 1 #Enclosing 就算底下有多層def 最多還是只會找到Enclosing 不會找到global # Enclosing 作用範圍在hi() ~ hey()之間 def hey(): # global a 的話會找到最外面的a = 0 # nonlocal a 的話會找到local外面的a,也就是Enclosing nonlocal a # <=== 這裡的 a 會去找到hi()底下的 a = 1 a = 5 # a = 5 會取代 hi()底下的 a print(a) # 5 hey() print(a) # 5 因為原本的a = 1被取代掉成為 a = 5,所以這裡會印出 5 hi() # <=== 執行 hi() print(a) # 0 <=== 這裡印出的是最外層全域變數 a ``` ![image](https://hackmd.io/_uploads/Hy_B2XfF0.png) [情境六] 那內建keyword來當作變數的影響 ```py a = 0 print = 789 # <=== 使用內建print當作變數指定789 def hi(): # Enclosing 作用範圍 a = 1 #Enclosing 就算底下有多層def 最多還是只會找到Enclosing 不會找到global print = 456 # <=== 使用內建print當作變數指定456 def hey(): nonlocal a print = 123 # <=== 使用內建print當作變數指定123 a = 2 print(a) hey() print(a) print(a) # <===== 發生錯誤 ``` ![image](https://hackmd.io/_uploads/S1XH6QGKC.png)