# 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),表示`進入`虛擬環境

4. 退出虛擬環境: deactivate
=> 命令列最前面的Pormpt (.venv)會消失不見,表示`退出`虛擬環境

- 如何將環境目前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,所以會發生錯誤

執行成功結果
```py
def hi(a: int, b: int, c: int):
print(a, b, c)
hi(1, 2, 3)
```

- 內建型別要注意不要用到 (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 置中對齊字,兩邊字串補上空白
```

- 時間補 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表示方式,後面必須要加上一個逗號
- 需要加逗號

- 無加上逗號會無法判斷為tuple,會被視為元素本身的型別

## 串列推導式 (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)",是`未展開形式`

※ 何謂疊代器(iterator): 就是一個物件,可以對這個物件的內容進行遍訪、輪巡的對象
2. 利用list()方法可以將zip()回傳的疊代器`展開`,並且把所有元素(元組)儲存在一列表中

[情境四]
```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
```

[情境六]
```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()

## Python Unpacking 開箱技巧 (多重指定,解構方式)
- 串列 list

- 元組 tuple

## 陣列 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()
```

[情境四] 使用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
```

[情境六] 那內建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) # <===== 發生錯誤
```
