---
# System prepended metadata

title: 20240726 Python
tags: [Python]

---

# 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)
