Python



Python常用連結

  • Python官方

    Python 的優勢

    簡潔易學的語法

    Python 的語法簡單且接近自然語言,即使是程式設計新手也能快速上手,降低了學習的門檻。

    豐富的標準庫與第三方資源

    Python 提供了廣泛的標準函式庫,涵蓋網路、數據處理、圖形界面等多方面。此外,強大的第三方生態系如 NumPy、Pandas、TensorFlow 使 Python 成為多功能的開發工具。

    跨平台的特性

    Python 是跨平台的語言,無論是 Windows、macOS 還是 Linux,都能執行相同的 Python 程式,極大提高了開發的靈活性。

    廣泛應用於多個領域

    Python 在多個領域中發揮重要作用,例如資料科學、人工智慧、網頁開發、自動化腳本和遊戲開發等,讓開發者可以用一種語言處理多種需求。

    活躍的社群支持

    Python 擁有一個龐大的全球社群,無論是初學者還是資深開發者,都能輕易找到教學資源、討論群組和技術支持。

    高效的開發速度

    Python 提供了直觀的語法和強大的工具,讓開發者能更快地實現程式設計,縮短產品的開發週期。



    Python 開發環境

    Anaconda

    什麼是 Anaconda?

    Anaconda 是一個開放源碼的 Python 和 R 編程平台,專為科學計算設計,包括數據科學、機器學習、人工智慧和大數據分析等應用。

    主要功能

    適合對象

    Anaconda 適合以下領域的使用者:

    如何安裝 Anaconda?

    1. 訪問 Anaconda 官方網站
    2. 選擇適合的作業系統版本,下載對應的安裝檔案。
    3. 按照安裝向導完成安裝,並配置環境變數(可選)。

    常見問題

    以下是使用者常遇到的問題:



    Anaconda 環境

    什麼是 Anaconda 環境

    Anaconda 提供了虛擬環境 (Environment) 功能,讓使用者可以在同一台電腦上建立多個彼此獨立的 Python 執行環境。每個環境可以有不同的 Python 版本與套件,避免不同專案間的依賴衝突。

    建立環境

    
    # 建立一個名為 myenv 的環境,並指定 Python 版本
    conda create -n myenv python=3.10
    

    啟動與切換環境

    
    # 啟動環境
    conda activate myenv
    
    # 退出環境
    conda deactivate
    

    查看環境

    
    # 列出所有環境
    conda env list
    # 或
    conda info --envs
    

    匯出與還原環境

    
    # 匯出環境配置到 YAML 檔案
    conda env export > environment.yml
    
    # 從 YAML 檔案建立環境
    conda env create -f environment.yml
    

    刪除環境

    
    # 刪除指定環境
    conda remove -n myenv --all
    


    Jupyter

    什麼是 Jupyter?

    Jupyter 是一個開放源碼的交互式計算環境,支援多種程式語言,主要用於數據科學、機器學習和學術研究。

  • Jupyter

    核心特點

    主要組件

    應用範圍

    Jupyter 被廣泛應用於以下領域:

    如何使用 Jupyter?

    1. 安裝 Anaconda 或獨立安裝 Jupyter。
    2. 在終端機輸入 jupyter notebook 啟動 Jupyter Notebook。
    3. 透過瀏覽器進入編輯介面,創建和運行 Notebook。

    優勢與挑戰



    VS Code Python 開發環境

    安裝Visual Studio Code

    前往 Visual Studio Code官方網站,下載並安裝適合您作業系統的版本。

    安裝Python擴展

    在Visual Studio Code中,透過以下步驟安裝Python擴展:

    1. 點擊左側的擴展圖示。
    2. 搜尋「Python」。
    3. 選擇由Microsoft提供的Python擴展並點擊「安裝」。

    安裝Python

    確保系統已安裝Python。可以從 Python官方網站 下載並安裝。

    安裝完成後,在命令列輸入以下指令確認安裝成功:

    python --version
    # 或
    python3 --version

    設定Python解譯器

    打開您的Python專案或檔案,點擊Visual Studio Code右下角的「Python」狀態欄,選擇適當的Python解譯器。

    執行Python程式

    在編輯器中開啟Python檔案,使用以下方式執行程式:

    1. 右鍵檔案內容,選擇「Run Python File in Terminal」。
    2. 或使用快捷鍵 Ctrl + Shift + P,搜尋「Run Python File」並執行。

    安裝必要套件

    如果需要安裝第三方套件,可以使用內建終端機輸入:

    pip install 套件名稱

    啟用自動完成與除錯

    透過Python擴展提供的功能,可享受自動完成與強大的除錯工具:

    1. 點擊左側的除錯圖示。
    2. 選擇「Create a launch.json file」,選擇Python。
    3. 設置完成後即可按F5啟用除錯模式。

    常用快捷鍵

    以下是幾個常用快捷鍵:



    VS Code

  • vscode/Vidual Studio Code

    VS Code設定Python執行參數

    修改launch.json

    若需要在執行Python程式時傳遞參數,可以透過設定 launch.json 完成:

    1. 點擊左側的「Run and Debug」圖示。
    2. 點擊「create a launch.json file」或「Add Configuration」。
    3. 選擇「Python」作為環境。
    4. 在生成的 launch.json 文件中修改相關設定。

    設定program與args參數

    以下是一個範例配置,包含程式路徑和執行時的參數:

    {
        "version": "0.2.0",
        "configurations": [
            {
                "name": "Python: Run with Arguments",
                "type": "python",
                "request": "launch",
                "program": "${workspaceFolder}/main.py",  // 程式路徑
                "console": "integratedTerminal",         // 終端類型
                "args": ["arg1", "arg2", "--option", "value"]  // 傳遞參數
            }
        ]
    }
    

    args的用途

    args 中可以傳遞命令列參數,例如:

    在程式中讀取參數

    使用 sys.argv 來讀取命令列傳遞的參數:

    import sys
    
    print("所有參數:", sys.argv)
    if len(sys.argv) > 1:
        print("第一個參數:", sys.argv[1])
        print("第二個參數:", sys.argv[2])

    執行範例

    假設程式為:

    python main.py arg1 arg2 --option value

    執行結果:

    所有參數: ['main.py', 'arg1', 'arg2', '--option', 'value']
    第一個參數: arg1
    第二個參數: arg2


    VS Code Python Debug 模式

    啟用 Debug 模式

    1. 安裝 Python Extension 擴展。

    2. 在 VS Code 中開啟您的 Python 專案。

    3. 按下 F5 或點擊左側活動欄的 Debug 圖示。

    設定 launch.json

    1. 點擊 Debug 面板中的「新增配置」。

    2. 選擇 Python,系統會自動生成一個 launch.json

    {
        "version": "0.2.0",
        "configurations": [
            {
                "name": "Python: Current File",
                "type": "python",
                "request": "launch",
                "program": "${file}",
                "console": "integratedTerminal"
            }
        ]
    }
    

    設置中斷點

    1. 在程式碼行號旁點擊以新增中斷點。

    2. 可使用條件中斷點:右鍵點擊中斷點並選擇「編輯條件」。

    調試功能

    檢查變數

    1. 在 Debug 面板的「變數」區域檢視目前變數狀態。

    2. 可在「監視」區域手動加入特定表達式。

    使用 Debug Console

    1. 在 Debug Console 中輸入 Python 指令以即時檢查程式狀態。

    2. 可執行變數查詢、呼叫函式等操作。



    VS Code 設定 Python 路徑

    步驟 1:安裝 Python 與 VS Code

    確保已安裝 Python 並將其加入系統環境變數,然後下載並安裝 Visual Studio Code。

    步驟 2:安裝 Python 擴充套件

    開啟 Visual Studio Code,點擊左側的 Extensions 圖示,搜尋 Python,然後安裝 Microsoft 提供的 Python 擴充套件。

    步驟 3:檢查 Python 安裝路徑

    在終端機輸入以下指令來確認 Python 的安裝路徑:

    which python

    或(Windows 系統):

    where python

    步驟 4:設定 Python 路徑

    在 Visual Studio Code 中,按 Ctrl + Shift + P,輸入並選擇 Python: Select Interpreter

    在清單中選擇正確的 Python 路徑。如果未顯示,請手動輸入完整路徑。

    步驟 5:確認設定

    開啟終端機並執行 python --version 來確認選定的 Python 解釋器版本正確。

    附加資訊

    如果需要特定專案的 Python 路徑,可以在專案根目錄新增 .vscode/settings.json 檔案,並加入以下內容:

    {
      "python.pythonPath": "你的 Python 完整路徑"
    }

    替換 你的 Python 完整路徑 為實際路徑。



    VS Code Python 環境切換

    在 VS Code 中開發時,決定使用 Anaconda 或其他環境,主要透過底層的解釋器(Interpreter)設定。這能確保你的套件依賴與程式執行環境完全隔離。


    1. 選擇 Python 環境的步驟


    2. 如何決定使用 Anaconda 或其他環境

    環境類型 適用情境 主要優勢
    Anaconda 資料科學、機器學習、深度學習 預裝大量科學運算庫,對底層二進位檔案(如 DLL)管理較強。
    Venv 一般網頁開發、自動化腳本 輕量、啟動速度快,僅包含執行必要的套件。

    3. 在 Conda 中管理與切換環境

    除了圖形介面,你也可以在 VS Code 內建的終端機使用指令來管理環境:


    4. 常見問題與解決方案



    在 Chromebook 執行 Python

    方法一:使用 Linux (Crostini)

    1. 在 Chromebook 設定中開啟「Linux (Beta)」或「開發人員 → Linux 開發環境」。
    2. 啟動 Linux 終端機,輸入:
      
      sudo apt update
      sudo apt install python3 python3-pip -y
          
    3. 輸入 python3 進入 Python 互動式環境。
    4. 若要安裝額外模組,可用 pip3 install 模組名稱

    方法二:使用線上編譯器

    方法三:安裝 Android App

    方法四:使用 VS Code

    1. 在 Linux (Crostini) 環境安裝 VS Code:
      
      sudo apt install wget gpg -y
      wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > packages.microsoft.gpg
      sudo install -o root -g root -m 644 packages.microsoft.gpg /usr/share/keyrings/
      sudo sh -c 'echo "deb [arch=amd64 signed-by=/usr/share/keyrings/packages.microsoft.gpg] \
      https://packages.microsoft.com/repos/code stable main" > /etc/apt/sources.list.d/vscode.list'
      sudo apt update
      sudo apt install code -y
          
    2. 在 VS Code 中安裝 Python 插件,即可進行程式開發。

    結論



    pip 使用指南

    1. 什麼是 pip?

    2. pip 的基本操作

    3. 進階功能

    4. 常見問題與解決方案

    5. pip 的最佳實踐



    pip/cache/http-v2 資料夾

    1. 什麼是 pip/cache/http-v2 資料夾?

    2. http-v2 資料夾的用途

    3. http-v2 資料夾的管理

    4. 注意事項



    Python 基本資料結構

    檢查變數型別

    使用 type()

    type() 函式可以回傳物件的型別。

    
    x = 10
    print(type(x))   # <class 'int'>
    
    y = "hello"
    print(type(y))   # <class 'str'>
    

    使用 isinstance()

    isinstance() 用來檢查變數是否屬於某型別,支援多型別檢查。

    
    x = 10
    
    print(isinstance(x, int))         # True
    print(isinstance(x, str))         # False
    print(isinstance(x, (int, float))) # True
    

    差異



    Python 布林資料型態

    Python 中的布林(Boolean)是程式設計中表示兩種邏輯狀態的基本資料型態:真 (True) 或 假 (False)。它是所有條件判斷和流程控制的基礎。

    1. 布林值與類型

    a = True
    b = False
    print(type(a))  # 輸出: <class 'bool'>

    2. 布林與數字的關係

    在 Python 內部,bool 是整數 int 的子類別,因此它們可以參與數值運算:

    print(True + 1)   # 輸出: 2 (1 + 1)
    print(False * 5)  # 輸出: 0 (0 * 5)

    3. 核心用途:比較運算子

    比較運算子會對兩個值進行比較,並返回一個布林結果。

    運算子 描述 範例 結果
    == 等於 10 == 10 True
    != 不等於 5 != 10 True
    > 大於 10 > 5 True
    <= 小於或等於 5 <= 5 True

    4. 邏輯運算子

    邏輯運算子用於結合或修改布林值或布林表達式。

    print(True and False) # 輸出: False
    print(True or False)  # 輸出: True
    print(not True)       # 輸出: False

    5. 真值測試 (Truth Value Testing)

    在 Python 中,所有物件都具備真值。當物件用於條件判斷時,Python 會將其轉換為 TrueFalse

    if []: # 空列表被視為 False
        print("這不會被執行")
    else:
        print("列表為空")
    
    if "hello": # 非空字串被視為 True
        print("字串不為空")


    Python 陣列

    什麼是陣列?

    在 Python 中,陣列是一種用於存儲多個相同類型元素的資料結構。雖然 Python 本身並沒有內建的陣列型別,但可以使用 listarray 模組來實現類似的功能。

    使用 List 作為陣列

    list 是 Python 的內建資料結構,可以儲存多種類型的資料,但也可以用來模擬陣列。

    my_list = [1, 2, 3, 4, 5]
    print(my_list[0])  # 輸出: 1

    使用 array 模組

    如果需要真正的陣列(所有元素必須是相同類型),可以使用 array 模組。

    import array
    
    my_array = array.array('i', [1, 2, 3, 4, 5])
    print(my_array[0])  # 輸出: 1

    在這裡,'i' 表示陣列中的元素是整數。

    array 模組的基本操作

    以下是一些基本操作:

    陣列與 NumPy

    對於需要進行數值運算的情況,numpy 提供了更強大的陣列支援。

    import numpy as np
    
    my_numpy_array = np.array([1, 2, 3, 4, 5])
    print(my_numpy_array[0])  # 輸出: 1

    NumPy 陣列支援多維資料與向量化運算,非常適合處理大量數據。

    結論

    Python 提供多種方式來實現陣列功能,list 適用於一般情況,array 模組適合需要相同類型元素的情況,而 numpy 是科學計算的首選工具。



    判斷列表是否為空

    對於變數 ret_value = [],在 Python 語言中判斷它是否為空列表,有幾種簡潔高效的方法。由於空列表在布林運算中會被視為 False,以下是幾種常用的判斷方式。

    1. 使用布林求值 (最推薦的 Pythonic 方式)

    這是最受歡迎的方法,直接檢查列表的布林值。如果列表是空的,not 運算子會使其條件判斷為真(True)。

    ret_value = []
    
    if not ret_value:
        # 列表為空時執行此處
        print("ret_value 是一個空列表")
    else:
        # 列表不為空時執行此處
        print("ret_value 不是一個空列表")

    2. 使用 len() 函數

    檢查列表的長度是否等於 0。

    ret_value = []
    
    if len(ret_value) == 0:
        print("ret_value 是一個空列表")
    else:
        print("ret_value 不是一個空列表")

    3. 使用等值比較 == []

    直接將變數與一個空的列表字面值 [] 進行比較。

    ret_value = []
    
    if ret_value == []:
        print("ret_value 是一個空列表")
    else:
        print("ret_value 不是一個空列表")


    陣列的動態增減

    使用 List 進行動態操作

    在 Python 中,list 是動態資料結構,可以輕鬆進行元素的新增與移除。

    新增元素

    可以使用以下方法新增元素:

    # 新增元素示例
    my_list = [1, 2, 3]
    my_list.append(4)  # [1, 2, 3, 4]
    my_list.insert(1, 10)  # [1, 10, 2, 3, 4]
    my_list.extend([5, 6])  # [1, 10, 2, 3, 4, 5, 6]
    

    移除元素

    可以使用以下方法移除元素:

    # 移除元素示例
    my_list = [1, 2, 3, 4, 5]
    my_list.pop()  # [1, 2, 3, 4]
    my_list.remove(2)  # [1, 3, 4]
    my_list.clear()  # []
    

    使用 array 模組的動態操作

    對於需要相同類型元素的情況,可以使用 array 模組。

    新增元素

    append()extend() 方法適用於 array 模組。

    import array
    my_array = array.array('i', [1, 2, 3])
    my_array.append(4)  # [1, 2, 3, 4]
    my_array.extend([5, 6])  # [1, 2, 3, 4, 5, 6]
    

    移除元素

    remove()pop() 方法可用於 array 模組。

    # 移除元素示例
    my_array = array.array('i', [1, 2, 3, 4])
    my_array.remove(2)  # [1, 3, 4]
    my_array.pop()  # [1, 3]
    

    結論

    Python 提供了多種方法來實現陣列的動態增減,listarray 模組分別適合不同需求。對於更多功能需求,也可以考慮使用 numpy



    Python 陣列計數

    在 Python 中,list.count() 是一個專門用來統計特定元素出現次數的方法。它與取得列表長度的 len() 不同,必須傳入一個參數作為比對對象。


    1. list.count() 的基本語法

    語法為 list.count(value),它會回傳該值在列表中出現的整數次數。如果值不存在,則回傳 0。

    fruits = ['apple', 'banana', 'apple', 'orange', 'apple']
    
    # 統計 'apple' 出現的次數
    apple_count = fruits.count('apple')
    print(apple_count)  # 輸出: 3
    
    # 統計不存在的元素
    grape_count = fruits.count('grape')
    print(grape_count)  # 輸出: 0

    2. 常見計數需求與工具對照

    根據你的需求(是找單一元素、多個元素,還是全部統計),有不同的最佳實踐:

    需求 推薦方法 範例代碼
    統計單一特定元素 list.count() arr.count(10)
    取得列表總長度 len() len(arr)
    統計所有元素的頻率 collections.Counter Counter(arr)
    條件計數 (如大於5) 生成器表達式 sum(1 for x in arr if x > 5)

    3. 進階工具:collections.Counter

    如果你需要一次知道列表中「所有元素」各自出現了幾次,使用 Counter 會比跑多次 count() 效率高得多(O(n) vs O(n^2))。

    from collections import Counter
    
    data = [1, 2, 2, 3, 3, 3, 4]
    counts = Counter(data)
    
    print(counts)        # 輸出: Counter({3: 3, 2: 2, 1: 1, 4: 1})
    print(counts[3])     # 取得數字 3 的次數: 3
    print(counts.most_common(1)) # 取得出現次數最多的元素

    4. 容易混淆的用法提醒


    5. 實戰技巧:條件計數

    如果你想統計符合某種邏輯(例如長度大於 3 的字串)的元素個數,可以結合 sum()

    words = ['hi', 'hello', 'python', 'a', 'code']
    
    # 統計長度 > 3 的單字數量
    big_word_count = sum(1 for w in words if len(w) > 3)
    print(big_word_count) # 輸出: 3


    合併二維清單

    需求說明

    範例程式

    
    list1 = [
        ["a", 1],
        ["b", 2],
        ["c", 3]
    ]
    
    list2 = [
        ["c", 30],
        ["a", 10],
        ["d", 40]
    ]
    
    # 將 list1 轉成 dict
    dict1 = {k: v for k, v in list1}
    dict2 = {k: v for k, v in list2}
    
    # 找出所有的 key
    all_keys = sorted(set(dict1.keys()) | set(dict2.keys()))
    
    # 合併結果
    merged = []
    for k in all_keys:
        v1 = dict1.get(k)
        v2 = dict2.get(k)
        merged.append([k, v1, v2])
    
    for row in merged:
        print(row)
    

    輸出結果

    
    ['a', 1, 10]
    ['b', 2, None]
    ['c', 3, 30]
    ['d', None, 40]
    

    結論



    同時迴圈兩個list

    在 Python 中,若您有兩個列表(例如 selected_fields_listaSqlValuesStr_list),並需要同時遍歷它們,最常用且最 Pythonic 的方法是使用內建的 zip() 函式。

    1. 使用 zip() 函式

    zip() 函式會將多個可迭代對象(Iterable)打包成一個元組 (tuple) 的序列。每次迴圈迭代都會從每個列表中取出對應位置的元素。

    import re
    
    # 假設這是您 re.split() 之後得到的兩個列表
    selected_fields = "name,age,city"
    aSqlValuesStr = "Alice,25,Taipei"
    
    selected_fields_list = re.split(r',', selected_fields)
    aSqlValuesStr_list = re.split(r',', aSqlValuesStr)
    
    # 執行 for 迴圈
    for field, value in zip(selected_fields_list, aSqlValuesStr_list):
        print(f"欄位: {field}, 值: {value}")

    2. 範例輸出

    欄位: name, 值: Alice
    欄位: age, 值: 25
    欄位: city, 值: Taipei

    3. zip() 的重要注意事項

    當您使用 zip() 函式時,需要注意以下幾點:

    # 範例:列表長度不一致
    list1 = [1, 2, 3]
    list2 = ['a', 'b']
    
    for item1, item2 in zip(list1, list2):
        print(item1, item2)
    # 輸出只會有:
    # 1 a
    # 2 b


    對齊顯示二維陣列

    使用 format 對齊欄位

    
    data = [
        [1, 23, 456],
        [7890, 12, 3],
        [45, 678, 9]
    ]
    
    # 計算每一欄的最大寬度
    col_widths = [max(len(str(row[i])) for row in data) for i in range(len(data[0]))]
    
    # 格式化輸出
    for row in data:
        print("  ".join(str(val).rjust(col_widths[i]) for i, val in enumerate(row)))
    

    輸出結果

    
       1   23  456
    7890   12    3
      45  678    9
    

    使用 tabulate 模組

    第三方套件 tabulate 可直接輸出對齊的表格。

    
    from tabulate import tabulate
    
    data = [
        [1, 23, 456],
        [7890, 12, 3],
        [45, 678, 9]
    ]
    
    print(tabulate(data, tablefmt="grid"))
    

    輸出結果

    
    +------+-----+-----+
    |    1 |  23 | 456 |
    | 7890 |  12 |   3 |
    |   45 | 678 |   9 |
    +------+-----+-----+
    

    結論



    二維陣列浮點數對齊輸出

    說明

    以下範例示範如何印出二維清單,當元素為 float 時固定顯示兩位小數,其餘元素照原樣輸出,同時保持欄位對齊。

    程式範例

    
    data = [
        [1, 23.456, 456],
        [7890.1, 12, 3.5],
        [45, 678.9, 9]
    ]
    
    # 格式化每個元素字串
    formatted_data = []
    for row in data:
        new_row = []
        for val in row:
            if isinstance(val, float):
                s = f"{val:.2f}"   # float -> 兩位小數
            else:
                s = str(val)
            new_row.append(s)
        formatted_data.append(new_row)
    
    # 計算每一欄最大寬度
    col_widths = [max(len(row[i]) for row in formatted_data) for i in range(len(formatted_data[0]))]
    
    # 格式化輸出
    for row in formatted_data:
        print("  ".join(val.rjust(col_widths[i]) for i, val in enumerate(row)))
    

    輸出結果

    
        1  23.46  456
    7890.10     12  3.50
       45  678.90     9
    

    重點



    從字典列表中提取所有值

    您提供的資料結構是一個包含多個字典的列表,其中每個字典都代表一條時間序列記錄(例如 K 線資料)。

    data = [
        {'time': 1759028400000, 'open': '109398.3', 'close': '109364.8', 'high': '109489.2', 'low': '109364.8', 'volume': '518.7594'},
        {'time': 1759024800000, 'open': '109305.6', 'close': '109398.3', 'high': '109496.4', 'low': '109296.0', 'volume': '757.0290'},
        # ...
    ]

    如果您想要獲取所有記錄中的所有值(1759028400000, '109398.3', '109364.8' 等),並將它們收集在一個列表中,您可以使用巢狀的列表推導式(Nested List Comprehension)。

    1. 提取所有值到單一列表 (扁平化處理)

    使用兩層列表推導式來遍歷列表中的每一個字典,然後對每個字典調用 .values() 方法獲取值,最後將所有值收集到一個扁平的列表中。

    data = [
        {'time': 1759028400000, 'open': '109398.3', 'close': '109364.8', 'high': '109489.2', 'low': '109364.8', 'volume': '518.7594'},
        {'time': 1759024800000, 'open': '109305.6', 'close': '109398.3', 'high': '109496.4', 'low': '109296.0', 'volume': '757.0290'}
    ]
    
    all_values = [
        value 
        for record in data     # 遍歷外部列表中的每一個字典 record
        for value in record.values() # 遍歷字典 record 中的所有值
    ]
    
    print(all_values)

    2. 輸出結果

    這會得到一個包含所有數值的單一列表:

    [1759028400000, '109398.3', '109364.8', '109489.2', '109364.8', '518.7594', 1759024800000, '109305.6', '109398.3', '109496.4', '109296.0', '757.0290']

    3. 提取特定欄位的值

    如果您只需要提取特定的欄位(例如只需要所有的 open 價格),可以使用單層列表推導式:

    data = [
        {'time': 1759028400000, 'open': '109398.3', ...},
        {'time': 1759024800000, 'open': '109305.6', ...}
    ]
    
    open_prices = [record['open'] for record in data]
    
    print(open_prices)

    輸出結果:

    ['109398.3', '109305.6']


    從字典列表中提取特定鍵的值

    1. 使用列表推導式 (List Comprehension) (推薦) ✅

    列表推導式是一種簡潔的單行語法,用於從現有列表創建新列表。

    Python 實作範例

    from datetime import datetime
    
    data_list = [
        {'videoid': 'b5HxsaM_E2Y', 'publishedat': datetime(2025, 12, 7, 3, 0, 53), 'rankno': 7, 'viewcount': 913053, 'query': '棒球'}, 
        {'videoid': 'FEbMCBxsoWI', 'publishedat': datetime(2025, 11, 25, 5, 28, 6), 'rankno': 13, 'viewcount': 754598, 'query': '棒球'}, 
        {'videoid': 'nOJUI0PGB68', 'publishedat': datetime(2025, 12, 7, 3, 7, 46), 'rankno': 14, 'viewcount': 748349, 'query': '棒球'}, 
        {'videoid': 'uMHXIudw_w0', 'publishedat': datetime(2025, 12, 2, 10, 1, 38), 'rankno': 8, 'viewcount': 687949, 'query': '棒球'}
    ]
    
    target_key = 'videoid'
    
    # 使用列表推導式:對於 data_list 中的每個字典 item,取出 item[target_key]
    video_ids = [item[target_key] for item in data_list]
    
    print(f"提取的鍵: {target_key}")
    print("所有 videoid 的值:")
    print(video_ids)

    輸出結果

    ['b5HxsaM_E2Y', 'FEbMCBxsoWI', 'nOJUI0PGB68', 'uMHXIudw_w0']

    2. 處理鍵可能不存在的情況 (安全存取)

    如果列表中某些字典可能缺少目標鍵(例如某些字典沒有 `videoid` 鍵),直接使用 `item[target_key]` 會拋出 `KeyError` 錯誤。您可以使用字典的 `.get()` 方法或條件判斷來安全處理。

    Python 實作範例 (安全存取)

    data_with_missing_key = [
        {'videoid': 'A1', 'query': '足球'}, 
        {'query': '籃球'}, # 缺少 'videoid' 鍵
        {'videoid': 'C3', 'query': '排球'}
    ]
    
    target_key = 'videoid'
    
    # 方案 A: 使用 .get() 設置預設值 None(或任何其他值)
    safe_video_ids_A = [item.get(target_key) for item in data_with_missing_key]
    # 輸出: ['A1', None, 'C3']
    
    # 方案 B: 僅提取具有該鍵的值
    safe_video_ids_B = [item[target_key] for item in data_with_missing_key if target_key in item]
    # 輸出: ['A1', 'C3']
    
    print(f"\n安全提取結果 (方案 B): {safe_video_ids_B}")


    將 dict values 轉換為列表

    您從 onesymbollist.values() 獲得的結果是 Python 字典的視圖物件 dict_values。雖然它看起來像一個列表,但它是一個**動態的視圖**,並不是一個可供索引或修改的標準列表(List)。

    dict_values([1763510400000, '0.00015218', '0.00015336', '0.00015415', '0.00015067', '1634523'])

    要在不顯示 dict_values() 標籤的情況下獲取其內容,最簡單且最常見的方法是使用內建的 list() 函式將其強制轉換為列表。

    1. 轉換為列表 (list)

    dict_values 傳遞給 list() 函式,會立即將該視圖物件中的所有元素複製到一個新的標準列表中。

    # 假設這是您獲得的 dict_values 視圖物件
    dict_values_object = your_dictionary.values() # 假設 onesymbollist 是一個字典
    
    # 步驟:使用 list() 函式轉換
    result_list = list(dict_values_object)
    
    print(result_list)

    輸出結果

    [1763510400000, '0.00015218', '0.00015336', '0.00015415', '0.00015067', '1634523']

    2. 轉換為元組 (tuple)

    如果您的目標是創建一個不可變(immutable)的序列,您可以使用 tuple() 函式將其轉換為元組。

    result_tuple = tuple(dict_values_object)
    
    print(result_tuple)

    輸出結果

    (1763510400000, '0.00015218', '0.00015336', '0.00015415', '0.00015067', '1634523')

    3. 直接迭代 (不需轉換)

    請注意,如果您只是想在迴圈中逐一處理這些值,您不需要將其明確轉換為列表。dict_values 本身就是一個可迭代對象(Iterable)。

    # 假設這是您獲得的 dict_values 視圖物件
    dict_values_object = your_dictionary.values()
    
    print("逐一輸出元素:")
    for value in dict_values_object:
        print(value)

    總結來說,使用 list(your_dict.values()) 是獲取乾淨、可操作值列表的最常用方法。



    將字典值轉換為逗號分隔的字串

    要從 dict_values 視圖物件中提取所有的值,並將它們組合成一個不含外部括號、只用逗號和空格分隔的單一字串(例如:1763510400000, '0.00015218', ...),您需要結合以下步驟:

    1. 從字典中取得 dict_values 視圖物件。
    2. 將這個視圖物件中的所有元素轉換為字串型態。
    3. 使用 Python 的 ", ".join() 方法將這些字串連接起來。

    1. Python 實作

    假設您的原始字典名為 onesymbol_dict,並且您需要將非字串型的值(例如時間戳)轉換為字串,才能用 .join() 進行連接。

    onesymbol_dict = {
        'time': 1763510400000, 
        'open': '0.00015218', 
        'close': '0.00015336', 
        'high': '0.00015415', 
        'low': '0.00015067', 
        'volume': '1634523'
    }
    
    # 步驟 1 & 2: 獲取值並將所有值轉換為字串
    # 使用列表推導式確保所有元素都是字串
    values_as_strings = [str(v) for v in onesymbol_dict.values()]
    
    # 步驟 3: 使用 ', '.join() 連接這些字串
    values_string = ", ".join(values_as_strings)
    
    print(values_string)

    2. 輸出結果

    這將產生一個單一的字串,其內容僅為值,沒有任何外部括號或 dict_values 標籤:

    1763510400000, 0.00015218, 0.00015336, 0.00015415, 0.00015067, 1634523

    3. 關於字串引號的說明

    請注意,在您期望的輸出範例中,除了時間戳以外的值都有單引號:1763510400000, '0.00015218', ...

    如果您的目標是要讓字串類型的數值在最終輸出中保留單引號,您需要在連接時手動加上引號。這通常在為 SQL 語句或特定格式準備字串時需要。

    # 額外步驟:手動處理引號,假設非整數值需要引號
    quoted_values = []
    for v in onesymbol_dict.values():
        if isinstance(v, (str, float)) or (isinstance(v, int) and v < 1000000000000): # 假設小數字串需要引號
            quoted_values.append(f"'{v}'")
        else:
            quoted_values.append(str(v))
    
    final_quoted_string = ", ".join(quoted_values)
    
    print(final_quoted_string)

    這樣會得到您預期的格式(假設 time 不需要引號,其他數值字串需要):

    1763510400000, '0.00015218', '0.00015336', '0.00015415', '0.00015067', '1634523'


    判斷字串是否為數字

    使用 str.isdigit()

    isdigit() 方法可以用於檢查字串是否只包含數字字符。

    # 示例
    string = "12345"
    if string.isdigit():
        print("是數字")
    else:
        print("不是數字")
    

    注意:isdigit() 無法處理小數點或負號。

    使用 str.replace() 處理小數

    如果需要檢查帶有小數點的字串,可以先移除小數點再使用 isdigit()

    # 示例
    string = "123.45"
    if string.replace(".", "").isdigit():
        print("是數字")
    else:
        print("不是數字")
    

    此方法不適用於負數。

    使用 try-except 轉換為數字

    最通用的方法是嘗試將字串轉換為浮點數或整數,並捕捉轉換失敗的異常。

    # 示例
    string = "-123.45"
    try:
        float(string)  # 可以改用 int(string) 來檢查整數
        print("是數字")
    except ValueError:
        print("不是數字")
    

    使用正則表達式

    正則表達式可以精確匹配數字,包括整數、小數與負數。

    # 示例
    import re
    
    string = "-123.45"
    pattern = r"^-?\d+(\.\d+)?$"
    if re.match(pattern, string):
        print("是數字")
    else:
        print("不是數字")
    

    結論

    對於簡單情況,可使用 isdigit()。對於更複雜的情況(如處理小數或負數),建議使用 try-except 或正則表達式。



    Python f-string

    f-string(格式化字串文字) 是 Python 3.6 引入的一種強大且高效的字串格式化方法。它提供了一種簡潔、可讀性高的方式,將變數、表達式的值嵌入到字串中。

    f-string 的核心特點是:在字串開頭使用前綴 fF,並使用大括號 {} 來包含要計算和顯示的內容。

    1. 基本用法:嵌入變數

    您可以直接將任何變數名稱放在大括號內。

    name = "Alice"
    age = 30
    message = f"Hello, my name is {name} and I am {age} years old."
    # 輸出: Hello, my name is Alice and I am 30 years old.

    2. 嵌入表達式

    f-string 的強大之處在於,您可以在大括號內放置任何有效的 Python 表達式,它會在運行時被求值。

    price = 19.99
    tax_rate = 0.05
    total = price * (1 + tax_rate)
    
    # 在 f-string 內執行計算
    result = f"含稅總價為: {price * (1 + tax_rate):.2f} 元。"
    # 輸出: 含稅總價為: 20.99 元。
    
    # 呼叫函式
    def get_status():
        return "OK"
    
    status_msg = f"系統狀態: {get_status()}"
    # 輸出: 系統狀態: OK

    3. 字串格式化與對齊

    f-string 支援與 .format() 方法相同的格式規範迷你語言(Format Specifier Mini-Language),使用冒號 : 來分隔表達式和格式說明符。

    格式碼 用途 範例 輸出
    :.2f 浮點數小數點後兩位 f"{3.14159:.2f}" 3.14
    : <10 靠左對齊,寬度 10 f"{'Name':<10}" Name
    : >10 靠右對齊,寬度 10 f"{'Value':>10}" Value
    :^10 置中對齊,寬度 10 f"{'Hi':^10}" Hi
    :, 數字千位分隔符 f"{1000000:,}" 1,000,000

    4. 偵錯功能 (Debug F-strings)

    從 Python 3.8 開始,f-string 引入了一個方便的偵錯功能,可以在變數後加上等號 =,自動顯示變數名稱及其值。

    user_id = 42
    is_active = True
    
    debug_output = f"User ID is {user_id=}, Status: {is_active=}"
    # 輸出: User ID is user_id=42, Status: is_active=True

    5. 注意事項



    f-string 不顯示浮點數小數點

    1. 方案一:在 f-string 內進行整數轉換 (推薦)

    在 f-string 的大括號 {} 內部,您可以直接使用 int() 函式將變數轉換回整數類型。這是最清晰且最直接的方法。

    Python 實作範例

    inta = 12 # 假設原始整數
    float_a = inta * 2.0 # 結果是 24.0 (浮點數)
    
    # 直接在 f-string 中將浮點數轉換回整數
    result_str = f"abc def {int(float_a)}"
    
    print(f"原始值 (float_a): {float_a}")
    print(f"格式化結果: {result_str}")

    2. 方案二:使用 f-string 格式化說明符 (Format Specifier)

    您可以使用格式化迷你語言中的整數格式碼 d.0f 來控制輸出格式。

    A. 使用整數格式碼 :d

    使用 :d 會要求 Python 在顯示時將該值視為整數。如果變數 a 是浮點數,Python 會自動將其四捨五入到最接近的整數後再顯示(如果您的計算結果是 $24.0$,它會顯示 $24$)。

    float_a = 24.0
    result_d = f"abc def {float_a:d}" 
    # 輸出: abc def 24

    B. 使用浮點數格式碼 :.0f

    使用 :.0f 意味著將該值格式化為浮點數,但要求小數點後顯示 0 位數字。這也會導致結果被四捨五入後顯示。

    float_a = 24.0
    result_0f = f"abc def {float_a:.0f}"
    # 輸出: abc def 24

    總結與建議

    如果您的目的是確保結果是一個絕對的整數且不帶小數點,推薦使用方案一:

    str = f"abc def {int(a)}"


    Python re.split 函式

    在 Python 中,re.split() 函式是 re(正規表達式)模組中的一個強大工具,用於根據正規表達式中定義的分隔符號(模式)來切割字串,並將結果返回為一個列表(list)。

    1. 函式語法

    re.split(pattern, string, maxsplit=0, flags=0)

    2. 基本用法

    使用正規表達式來定義多個或複雜的分隔符號。

    import re
    
    text = "apple,banana;orange-grape"
    # 使用逗號、分號或連字號作為分隔符號
    result = re.split(r'[;,-]', text)
    
    print(result)
    # 輸出: ['apple', 'banana', 'orange', 'grape']

    3. 處理多個空格 (一個常見用途)

    與標準字串的 split() 不同,re.split() 可以輕鬆處理多個連續的分隔符號(例如多個空格),並忽略它們。

    text = "Word1   Word2  Word3"
    # 使用 \s+ 匹配一個或多個空白字元作為分隔符號
    result = re.split(r'\s+', text)
    
    print(result)
    # 輸出: ['Word1', 'Word2', 'Word3']

    4. 使用 maxsplit 限制切割次數

    如果設定了 maxsplit,切割操作只會執行指定的次數,剩下的部分會作為最後一個元素保留在列表中。

    text = "one:two:three:four"
    # 只切割一次
    result = re.split(r':', text, maxsplit=1)
    
    print(result)
    # 輸出: ['one', 'two:three:four']

    5. 保留分隔符號

    如果將分隔符號模式放置在括號 () 中,則分隔符號本身也會包含在結果列表的元素之間。

    text = "2025-01-15"
    # 將連字號放在括號中,使其被保留
    result = re.split(r'(-)', text)
    
    print(result)
    # 輸出: ['2025', '-', '01', '-', '15']


    判斷字串開頭 startswith

    用途

    startswith() 是 Python 字串(str)物件的方法,用來判斷字串是否以指定的子字串開頭。 若符合,回傳 True;否則回傳 False

    語法

    
    str.startswith(prefix[, start[, end]])
    

    參數說明

    回傳值

    布林值:若字串以指定字首開頭,回傳 True,否則 False

    範例

    
    text = "Python Programming"
    
    # 基本用法
    print(text.startswith("Py"))        # True
    print(text.startswith("Java"))      # False
    
    # 指定範圍
    print(text.startswith("thon", 2))   # True (從索引 2 開始是 "thon")
    
    # 多重比對
    print(text.startswith(("Py", "Java", "C")))  # True,因為有任一符合
    
    # 不區分大小寫(可先轉小寫)
    print(text.lower().startswith("py"))  # True
    

    常見應用

    延伸

    若要判斷字串是否以某段文字「結尾」,可使用 endswith() 方法,語法與 startswith() 相同。

    
    filename = "report.pdf"
    if filename.endswith(".pdf"):
        print("這是 PDF 檔案")
    


    刪除字串最後一字元

    說明

    在 Python 中,字串是不可變(immutable)物件, 若要刪除最後一個字元,通常使用字串切片(slicing)建立新的字串。

    範例

    
    text = "Hello!"
    
    # 方法一:使用切片
    new_text = text[:-1]
    print(new_text)   # 輸出: Hello
    
    # 方法二:使用 rstrip() 移除特定結尾字元
    text2 = "Hello!!!"
    new_text2 = text2.rstrip("!")
    print(new_text2)  # 輸出: Hello
    
    # 方法三:確保非空再刪除最後一字元
    if text:
        text = text[:-1]
    print(text)
    

    輸出結果

    
    Hello
    Hello
    Hello
    

    說明

    延伸

    若要刪除開頭字元,可使用:

    
    text = text[1:]
    

    總結



    字串中找到子字串之前的內容

    問題說明

    給定一個字串 str1,我們希望找到在 strAstrB 出現之前的部分。例如:

    str1 = "Hello World, this is a test. Stop here or continue."
    strA = "Stop"
    strB = "continue"
    

    目標是獲取 "Hello World, this is a test. "

    使用 re.split()

    re.split() 可以根據多個關鍵字拆分字串,並取第一部分:

    import re
    
    def get_substring_before(text, strA, strB):
        result = re.split(f"{re.escape(strA)}|{re.escape(strB)}", text, maxsplit=1)[0]
        return result
    
    str1 = "Hello World, this is a test. Stop here or continue."
    strA = "Stop"
    strB = "continue"
    
    print(get_substring_before(str1, strA, strB))  # "Hello World, this is a test. "
    

    使用 re.search()

    re.search() 可以用來匹配 strAstrB,並取得匹配前的內容:

    import re
    
    def get_substring_before(text, strA, strB):
        match = re.search(f"{re.escape(strA)}|{re.escape(strB)}", text)
        return text[:match.start()] if match else text
    
    str1 = "Hello World, this is a test. Stop here or continue."
    print(get_substring_before(str1, "Stop", "continue"))  # "Hello World, this is a test. "
    

    使用 find() 方法

    find() 方法可以手動搜尋最早出現的 strAstrB,然後擷取對應部分:

    def get_substring_before(text, strA, strB):
        indexA = text.find(strA)
        indexB = text.find(strB)
        
        indices = [i for i in [indexA, indexB] if i != -1]
        first_index = min(indices, default=len(text))
        
        return text[:first_index]
    
    str1 = "Hello World, this is a test. Stop here or continue."
    print(get_substring_before(str1, "Stop", "continue"))  # "Hello World, this is a test. "
    

    結論



    多個值串接成字串

    用 join(需先全部轉成字串)

    values = ["str1", "str2", 123, "str3", 456]
    
    s = ", ".join(str(v) for v in values)
    print(s)  # 輸出: str1, str2, 123, str3, 456
    

    自動處理字串與整數混合

    def join_values(*args, sep=", "):
        return sep.join(str(v) for v in args)
    
    print(join_values("str1", "str2", 88, "str3"))
    # 輸出: str1, str2, 88, str3
    

    若來源是 dict,依 key 排序後串接

    data = {
        "str1": "hello",
        "str2": "world",
        "int1": 123,
        "str3": "ok"
    }
    
    # 按 key 排序後 join
    s = ", ".join(str(data[k]) for k in sorted(data.keys()))
    print(s)  # hello, world, 123, ok
    

    若要 key + value 一起 join

    s = ", ".join(f"{k}={v}" for k, v in data.items())
    print(s)
    # 輸出: str1=hello, str2=world, int1=123, str3=ok
    

    來源是 list of dict,每個 dict join 成一列

    rows = [
        {"str1": "A", "int1": 10},
        {"str1": "B", "int1": 20},
    ]
    
    for row in rows:
        print(", ".join(str(v) for v in row.values()))
    # A, 10
    # B, 20
    

    通用函式:可切換「有引號」或「無引號」模式

    def join_values(values, sep=", ", quoted=False):
        if quoted:
            # 使用全形單引號 ‘ ’
            return sep.join(f"‘{v}’" for v in values)
        else:
            return sep.join(str(v) for v in values)
    
    values = ["str1", "str2", 88, "str3"]
    
    print(join_values(values, quoted=False))
    # 輸出: str1, str2, 88, str3
    
    print(join_values(values, quoted=True))
    # 輸出: ‘str1’, ‘str2’, ‘88’, ‘str3’
    

    可支援 *args 輸入版本

    def join_args(*args, sep=", ", quoted=False):
        if quoted:
            return sep.join(f"‘{v}’" for v in args)
        return sep.join(str(v) for v in args)
    
    print(join_args("str1", "str2", 88, "str3", quoted=True))
    # ‘str1’, ‘str2’, ‘88’, ‘str3’
    

    支援 dict(只輸出 value)

    data = {"str1": "hello", "str2": "world", "int1": 123}
    
    print(join_values(data.values(), quoted=True))
    # ‘hello’, ‘world’, ‘123’
    

    支援 key=value 格式

    def join_key_value(d, sep=", ", quoted=False):
        if quoted:
            return sep.join(f"{k}=‘{v}’" for k, v in d.items())
        return sep.join(f"{k}={v}" for k, v in d.items())
    
    print(join_key_value(data, quoted=True))
    # str1=‘hello’, str2=‘world’, int1=‘123’
    


    re.match()

    re.match()

    Python 的 re.match 是正則表達式模組中的一個函式,用於從字串的開頭進行匹配。 如果匹配成功,則返回一個 Match 物件;否則返回 None

    語法

    re.match(pattern, string, flags=0)

    參數說明:

    常用屬性和方法

    使用範例

    import re
    
    # 定義一個字串
    text = "123 Hello World!"
    
    # 使用 re.match 從開頭匹配數字
    match = re.match(r"(\d+)\s+(.*)", text)
    
    if match:
        print(f"整個匹配結果: {match.group(0)}")  # 123 Hello World!
        print(f"數字部分: {match.group(1)}")      # 123
        print(f"文字部分: {match.group(2)}")      # Hello World!
    else:
        print("匹配失敗")
    

    輸出結果

    
    整個匹配結果: 123 Hello World!
    數字部分: 123
    文字部分: Hello World!
        

    注意事項

    正則表達式

    正則表達式(Regular Expression,簡稱 Regex)是一種用於描述字串匹配規則的語法,常用於搜尋、替換或驗證字串。 在 Python 的 re 模組中,pattern 就是定義這些規則的核心部分。

    基本語法元素

    進階用法

    範例

    import re
    
    # 例子 1:匹配數字開頭的內容
    pattern = r"^\d+"
    text = "123abc"
    match = re.match(pattern, text)
    if match:
        print(f"匹配結果: {match.group()}")  # 輸出: 123
    
    # 例子 2:匹配數字後的文字
    pattern = r"(\d+)\s+(.*)"
    text = "123 Hello World"
    match = re.match(pattern, text)
    if match:
        print(f"數字部分: {match.group(1)}")  # 輸出: 123
        print(f"文字部分: {match.group(2)}")  # 輸出: Hello World
    

    正則表達式的應用場景



    re.search() 的應用

    基本用法

    re.search() 用於在字串中搜尋符合正則表達式的第一個匹配項,並回傳 Match 物件,如果沒有匹配則回傳 None

    import re
    
    text = "Hello 2024!"
    match = re.search(r"\d+", text)
    
    if match:
        print("找到數字:", match.group())  # 2024
    

    返回 Match 物件

    re.search() 找到匹配時,會返回 Match 物件,可透過以下方法存取資訊:

    import re
    
    text = "Python 3.10 is great!"
    match = re.search(r"\d+\.\d+", text)
    
    if match:
        print("匹配內容:", match.group())  # 3.10
        print("起始索引:", match.start())  # 7
        print("結束索引:", match.end())    # 11
        print("範圍:", match.span())       # (7, 11)
    

    使用群組匹配

    透過括號 () 來建立群組,並使用 group(n) 來提取對應的匹配內容。

    import re
    
    text = "John Doe, Age: 25"
    match = re.search(r"(\w+) (\w+), Age: (\d+)", text)
    
    if match:
        print("姓氏:", match.group(1))  # John
        print("名字:", match.group(2))  # Doe
        print("年齡:", match.group(3))  # 25
    

    與 re.findall() 的比較

    re.search() 只回傳第一個匹配的結果,而 re.findall() 會回傳所有匹配結果。

    import re
    
    text = "Price: $10, Discount: $2, Tax: $1"
    
    match = re.search(r"\$\d+", text)
    print("re.search:", match.group())  # $10
    
    matches = re.findall(r"\$\d+", text)
    print("re.findall:", matches)  # ['$10', '$2', '$1']
    

    結論

    re.search() 適合用來找到第一個匹配的結果,並能透過 Match 物件獲取詳細資訊。對於多個匹配結果,則可使用 re.findall()



    正則表達式的非捕獲群組

    提高匹配效能

    在正則表達式中,(...) 會捕獲匹配內容,並存入 group(n),但非捕獲群組 (?:...) 只用來組織結構,不會影響群組編號,因此匹配速度更快。

    避免影響群組索引

    如果在正則表達式中使用 () 來組織匹配條件,會影響 group(n) 的編號。使用 (?:...) 則可確保群組索引不變。

    import re
    
    text = "2024-03-12"
    pattern = r"(\d{4})-(?:\d{2})-(\d{2})"
    
    match = re.search(pattern, text)
    print(match.group(1))  # 2024
    print(match.group(2))  # 12
    

    搭配 OR 運算符

    使用 (?:...|...) 可以讓 | 運算符影響匹配內容,但不影響群組存取。

    import re
    
    text = "bar123"
    pattern = r"(?:foo|bar|baz)\d+"
    
    match = re.search(pattern, text)
    print(match.group())  # bar123
    

    應用於 --user-data-dir 解析

    在解析 Chrome 參數時,使用 (?:...) 可確保匹配格式不影響群組編號。

    import re
    
    cmdline = '--user-data-dir="C:\\Users\\moirg\\AppData\\Local\\Google\\Chrome\\User Data"'
    
    match = re.search(r'--user-data-dir=(?:"([^"]+)"|(\S+))', cmdline)
    user_data_dir = match.group(1) or match.group(2)
    
    print(user_data_dir)  # C:\Users\moirg\AppData\Local\Google\Chrome\User Data
    

    結論

    (?:...) 在正則表達式中能提高效能,避免影響群組索引,並適用於 | 運算及特定條件匹配,使程式碼更高效且清晰。



    Python 的 datetime

    匯入模組

    import datetime

    取得現在時間

    now = datetime.datetime.now()
    print(now)

    建立指定時間

    dt = datetime.datetime(2025, 7, 2, 14, 30, 0)
    print(dt)

    格式化時間字串

    now = datetime.datetime.now()
    formatted = now.strftime("%Y-%m-%d %H:%M:%S")
    print(formatted)

    解析時間字串

    dt_str = "2025-07-02 14:30:00"
    parsed = datetime.datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S")
    print(parsed)

    時間加減

    now = datetime.datetime.now()
    delta = datetime.timedelta(days=7)
    next_week = now + delta
    print(next_week)

    取得今天日期

    today = datetime.date.today()
    print(today)

    比較日期

    dt1 = datetime.datetime(2025, 7, 1)
    dt2 = datetime.datetime(2025, 7, 2)
    print(dt1 < dt2)

    取得時間差

    dt1 = datetime.datetime(2025, 7, 1, 12, 0, 0)
    dt2 = datetime.datetime(2025, 7, 2, 14, 30, 0)
    diff = dt2 - dt1
    print(diff)
    print(diff.total_seconds())

    取得星期幾

    today = datetime.date.today()
    print(today.weekday())  # 0 = 星期一, 6 = 星期日


    datetime 時區問題

    offset-naive 與 offset-aware datetime 相減錯誤

    當一個 datetime 物件沒有時區(naive),另一個有時區(aware)時進行相減,就會產生:

    TypeError: can't subtract offset-naive and offset-aware datetimes
    

    檢查 datetime 是否為 naive 或 aware

    from datetime import datetime
    
    def is_aware(dt):
        return dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) is not None
    
    def is_naive(dt):
        return not is_aware(dt)
    
    dt1 = datetime.now()                 # naive
    dt2 = datetime.now().astimezone()    # aware
    
    print(is_naive(dt1), is_aware(dt1))
    print(is_naive(dt2), is_aware(dt2))
    

    解決方式 A:統一轉成 aware(推薦)

    適用於跨時區或需要精準時間計算的情況。

    from datetime import datetime, timezone
    
    sql_dt = sql_dt.replace(tzinfo=timezone.utc)     # SQL 資料轉 aware
    now_dt = datetime.now(timezone.utc)              # 現在時間用 aware
    
    diff = now_dt - sql_dt
    print(diff.total_seconds())
    

    解決方式 B:統一轉成 naive(不推薦,會忽略時區差)

    sql_dt = sql_dt.replace(tzinfo=None)
    now_dt = datetime.now()
    
    diff = now_dt - sql_dt
    

    總結



    計算串列的標準差

    說明

    在 Python 中,可以用內建模組 statisticsstdev()pstdev() 計算樣本標準差或母體標準差。

    範例

    
    import statistics
    
    ratios = [2.3, 2.8, 3.1, 2.5, 3.0]
    
    # 平均值
    average = sum(ratios) / len(ratios)
    
    # 樣本標準差(n-1)
    std_sample = statistics.stdev(ratios)
    
    # 母體標準差(n)
    std_population = statistics.pstdev(ratios)
    
    print(f"平均值: {average:.2f}")
    print(f"樣本標準差: {std_sample:.3f}")
    print(f"母體標準差: {std_population:.3f}")
    

    輸出結果

    
    平均值: 2.74
    樣本標準差: 0.303
    母體標準差: 0.271
    

    公式方式(不使用模組)

    若不想依賴 statistics 模組,可以用數學公式自行計算:

    
    ratios = [2.3, 2.8, 3.1, 2.5, 3.0]
    average = sum(ratios) / len(ratios)
    
    # 標準差(母體)
    variance = sum((x - average) ** 2 for x in ratios) / len(ratios)
    std_dev = variance ** 0.5
    
    print(f"標準差: {std_dev:.3f}")
    

    輸出結果

    
    標準差: 0.271
    

    差異說明

    總結



    Python 顯示 ANSI 顏色字串

    基本範例

    # ANSI 顏色碼範例
    print("\033[31m紅色文字\033[0m")
    print("\033[32m綠色文字\033[0m")
    print("\033[33m黃色文字\033[0m")
    print("\033[34m藍色文字\033[0m")
    print("\033[35m紫色文字\033[0m")
    print("\033[36m青色文字\033[0m")
    print("\033[37m白色文字\033[0m")
    

    粗體與背景色

    print("\033[1;31m粗體紅色文字\033[0m")
    print("\033[42m綠色背景文字\033[0m")
    

    自訂顏色組合

    # 格式: \033[樣式;前景色;背景色m
    # 樣式: 0=預設, 1=粗體, 4=底線
    # 前景色: 30~37
    # 背景色: 40~47
    
    print("\033[1;33;44m粗體黃色字 + 藍色背景\033[0m")
    

    可封裝成函式

    def color_text(text, color_code):
        return f"\033[{color_code}m{text}\033[0m"
    
    print(color_text("警告!", "1;31"))  # 粗體紅色
    print(color_text("成功!", "1;32"))  # 粗體綠色
    


    檢查目前終端機是否支援 ANSI 顏色

    方法一:檢查 sys.stdout.isatty()

    import sys
    
    if sys.stdout.isatty():
        print("終端機可能支援 ANSI 顏色")
    else:
        print("可能是檔案或不支援顏色的輸出環境")
    

    方法二:使用 colorama(跨平台解決方案)

    import colorama
    colorama.init()
    
    print("\033[32m這段文字應該會是綠色\033[0m")
    

    方法三:實際測試輸出

    def supports_ansi():
        try:
            print("\033[31m測試紅色\033[0m")
            return True
        except:
            return False
    
    print("支援 ANSI" if supports_ansi() else "不支援 ANSI")
    

    補充



    Python 輸入支援自動完成

    說明

    在 Python 中,input() 函式本身並不支援 Tab 自動完成。 若要實現這個功能,可以結合 readline 模組,讓使用者在互動式輸入時,使用 Tab 進行自動補全(類似 Bash 或 IPython)。

    基本範例

    
    import readline
    
    # 定義可補全的字串清單
    WORDS = ['apple', 'banana', 'cherry', 'grape', 'orange', 'watermelon']
    
    def completer(text, state):
        """自動完成函式:根據輸入字首比對可用字串"""
        options = [w for w in WORDS if w.startswith(text)]
        if state < len(options):
            return options[state]
        else:
            return None
    
    # 啟用補全功能
    readline.set_completer(completer)
    readline.parse_and_bind('tab: complete')
    
    # 使用者輸入(支援 Tab)
    user_input = input("輸入水果名稱(可按 Tab 補全): ")
    print(f"你輸入的是: {user_input}")
    

    運作說明

    進階:動態補全

    你也可以依照目前的上下文或動態內容更新補全清單:

    
    import readline
    
    def dynamic_completer(text, state):
        current_words = ['cat', 'car', 'dog', 'duck', 'deer']
        options = [w for w in current_words if w.startswith(text)]
        if state < len(options):
            return options[state]
        return None
    
    readline.set_completer(dynamic_completer)
    readline.parse_and_bind('tab: complete')
    
    command = input("輸入動物名稱: ")
    print("你輸入:", command)
    

    注意事項

    總結



    Python 流程

    Python 迴圈

    for 搭配 range()

    最常見的迴圈,用來跑固定次數或數列。

    
    # 從 0 到 4
    for i in range(5):
        print(i)  # 0,1,2,3,4
    
    # 指定起點、終點與步長
    for i in range(2, 10, 2):
        print(i)  # 2,4,6,8
    

    for 搭配可迭代物件

    直接迭代清單、字串、字典等。

    
    fruits = ["apple", "banana", "cherry"]
    
    for fruit in fruits:
        print(fruit)
    
    for ch in "hello":
        print(ch)
    
    # 迭代字典
    person = {"name": "Tom", "age": 25}
    for key, value in person.items():
        print(key, value)
    

    while 迴圈

    當條件為 True 就會一直執行。

    
    count = 0
    while count < 5:
        print(count)
        count += 1
    

    break 與 continue

    控制迴圈流程。

    
    for i in range(10):
        if i == 3:
            continue  # 跳過本次
        if i == 7:
            break     # 提前結束
        print(i)
    

    巢狀迴圈

    迴圈裡再放迴圈。

    
    for i in range(3):
        for j in range(2):
            print(f"i={i}, j={j}")
    

    迴圈搭配 else

    for 或 while 都可以加 else,只有「正常跑完(沒有 break)」才會執行。

    
    for i in range(5):
        print(i)
    else:
        print("迴圈正常結束")
    

    列表生成式 (List Comprehension)

    簡潔寫法,可以在一行內完成迴圈與生成清單。

    
    squares = [x**2 for x in range(5)]
    print(squares)  # [0,1,4,9,16]
    

    結論



    Python 異常處理

    基本語法結構

    Python 使用 try...except 語句來攔截並處理程式執行時發生的錯誤,防止程式崩潰。

    try:
        # 可能會發生異常的程式碼
        result = 10 / 0
    except ZeroDivisionError:
        # 發生特定異常時執行的代碼
        print("除數不能為零")
    except Exception as e:
        # 捕捉其他所有類型的異常
        print(f"發生錯誤: {e}")
    else:
        # 如果 try 區塊沒有發生異常則執行
        print("運作正常")
    finally:
        # 無論是否發生異常都一定會執行
        print("清理資源或關閉檔案")

    區塊功能說明

    捕捉多個異常

    您可以在一個 except 中使用元組(Tuple)來同時處理多個錯誤類型。

    try:
        # 執行操作
        pass
    except (ValueError, TypeError):
        print("輸入的資料類型或數值有誤")

    主動拋出異常

    使用 raise 關鍵字可以根據邏輯需求手動觸發異常。

    age = -1
    if age < 0:
        raise ValueError("年齡數值不可為負數")

    最佳實踐



    Python 類別

    1. 基本類別概念

    Python 的類別(Class)是用於封裝數據和行為的結構。類別用於創建物件,物件是類別的實例。例如:
    class MyClass:
        def __init__(self, value):
            self.value = value
    
        def display(self):
            print(f"Value: {self.value}")
    
    obj = MyClass(10)
    obj.display()  # 輸出: Value: 10
    

    2. 靜態方法(Static Method)

    靜態方法使用 `@staticmethod` 裝飾器定義,與類別和物件無關,不能訪問類別屬性或物件屬性。適用於一些工具性功能:

    class MyClass:
        @staticmethod
        def add(a, b):
            return a + b
    
    result = MyClass.add(5, 3)
    print(result)  # 輸出: 8
    

    3. 類別方法(Class Method)

    類別方法使用 `@classmethod` 裝飾器定義,第一個參數是類別本身(通常命名為 `cls`),可以訪問類別屬性:

    class MyClass:
        count = 0
    
        @classmethod
        def increment_count(cls):
            cls.count += 1
    
    MyClass.increment_count()
    print(MyClass.count)  # 輸出: 1
    

    4. 繼承與多型

    Python 支援類別繼承,子類可以繼承父類的屬性和方法,並覆寫父類方法:

    class Parent:
        def greet(self):
            print("Hello from Parent!")
    
    class Child(Parent):
        def greet(self):
            print("Hello from Child!")
    
    obj = Child()
    obj.greet()  # 輸出: Hello from Child!
    

    5. 類別屬性與物件屬性

    類別屬性是屬於整個類別的,所有物件共享;物件屬性則屬於每個物件:

    class MyClass:
        class_attr = "I am a class attribute"
    
        def __init__(self, value):
            self.instance_attr = value
    
    obj1 = MyClass(10)
    obj2 = MyClass(20)
    
    print(MyClass.class_attr)  # 輸出: I am a class attribute
    print(obj1.instance_attr)  # 輸出: 10
    print(obj2.instance_attr)  # 輸出: 20
    

    6. 使用 object 作為基類

    Python 中的所有類別都默認繼承自 `object`,這是一個內建的基類,提供一些基本方法,例如 `__str__` 和 `__eq__`:

    class MyClass(object):
        def __init__(self, value):
            self.value = value
    
        def __str__(self):
            return f"MyClass with value {self.value}"
    
    obj = MyClass(5)
    print(obj)  # 輸出: MyClass with value 5
    

    7. 總結

    - **靜態方法(Static Method)**:與類別無關,主要用於工具性功能。 - **類別方法(Class Method)**:操作類別層級的數據。 - **物件方法(Instance Method)**:操作物件層級的數據。 - **繼承與多型**:支援代碼重用與靈活設計。 - **object 基類**:提供基本方法,讓所有類別具備一致的行為。

    類別繼承

    1. 基本繼承概念

    在 Python 中,類別繼承允許子類(Derived Class)繼承父類(Base Class)的屬性和方法,實現代碼重用。例如:

    class Parent:
        def greet(self):
            print("Hello from Parent!")
    
    class Child(Parent):
        pass
    
    c = Child()
    c.greet()  # 輸出: Hello from Parent!
    

    2. 子類覆寫父類方法

    子類可以覆寫(Override)父類的方法,改寫其功能:

    class Parent:
        def greet(self):
            print("Hello from Parent!")
    
    class Child(Parent):
        def greet(self):
            print("Hello from Child!")
    
    c = Child()
    c.greet()  # 輸出: Hello from Child!
    

    3. 使用 super() 呼叫父類方法

    在子類中可以透過 `super()` 呼叫父類的方法,並在父類行為基礎上擴展:

    class Parent:
        def greet(self):
            print("Hello from Parent!")
    
    class Child(Parent):
        def greet(self):
            super().greet()
            print("Hello from Child!")
    
    c = Child()
    c.greet()
    # 輸出:
    # Hello from Parent!
    # Hello from Child!
    

    4. 多重繼承

    Python 支援多重繼承,子類可以同時繼承多個父類:

    class Parent1:
        def greet(self):
            print("Hello from Parent1!")
    
    class Parent2:
        def greet(self):
            print("Hello from Parent2!")
    
    class Child(Parent1, Parent2):
        pass
    
    c = Child()
    c.greet()  # 輸出: Hello from Parent1! (依據繼承順序)
    

    5. 方法解析順序(MRO)

    多重繼承使用 MRO(Method Resolution Order)確定方法的解析順序。可以使用 `__mro__` 屬性檢查:

    print(Child.__mro__)
    # 輸出: (, , , )
    

    6. 抽象基類

    使用 `abc` 模組定義抽象基類(Abstract Base Class),強制子類實現特定方法:

    from abc import ABC, abstractmethod
    
    class AbstractParent(ABC):
        @abstractmethod
        def greet(self):
            pass
    
    class Child(AbstractParent):
        def greet(self):
            print("Hello from Child!")
    
    c = Child()
    c.greet()  # 輸出: Hello from Child!
    

    7. 總結

    - 繼承讓類別代碼更具重用性和擴展性。 - 子類可以覆寫父類方法,並用 `super()` 呼叫父類方法。 - 支援多重繼承,但需注意方法解析順序(MRO)。 - 抽象基類可用於強制子類實現特定方法,適合介面設計。

    建立繼承 ClassB 的臨時類別

    範例程式碼

    class ClassB:
        def greet(self):
            print("Hello from ClassB!")
    
    # 動態建立繼承自 ClassB 的臨時類別
    TempClass = type('TempClass', (ClassB,), {
        'greet': lambda self: (print("Hello from TempClass!"), super(TempClass, self).greet())[0]
    })
    
    # 創建實例並測試
    temp = TempClass()
    temp.greet()
    

    解釋

    1. type() 函數:
      type('TempClass', (ClassB,), {...})
      - 'TempClass':新類別名稱。
      - (ClassB,):基礎類別的元組,這裡只有 ClassB。
      - {...}:新增的屬性或方法。
    2. Lambda 函數用於覆蓋方法:
      - 自訂 greet 方法先印出新訊息,再透過 super() 呼叫父類別的 greet

    輸出結果

    Hello from TempClass!
    Hello from ClassB!


    切換使用具相同介面的不同類別

    說明

    假設有兩個類別 AClassBClass,它們具有相同名稱與參數的函式。 我們希望能方便地切換使用哪一個類別,而不需要修改主要程式邏輯。

    範例程式

    
    class AClass:
        def process(self, data):
            print(f"AClass 處理: {data}")
    
        def result(self):
            return "結果來自 AClass"
    
    
    class BClass:
        def process(self, data):
            print(f"BClass 處理: {data}")
    
        def result(self):
            return "結果來自 BClass"
    
    
    # 可透過設定控制使用哪個類別
    USE_A = True
    
    # 動態選擇類別
    SelectedClass = AClass if USE_A else BClass
    
    # 建立實例並使用
    obj = SelectedClass()
    obj.process("測試資料")
    print(obj.result())
    

    輸出結果 (USE_A=True)

    
    AClass 處理: 測試資料
    結果來自 AClass
    

    輸出結果 (USE_A=False)

    
    BClass 處理: 測試資料
    結果來自 BClass
    

    更進階的寫法:使用工廠函式

    
    def get_class(name):
        mapping = {
            "A": AClass,
            "B": BClass
        }
        return mapping.get(name, AClass)  # 預設用 AClass
    
    # 動態選擇
    cls = get_class("B")
    obj = cls()
    obj.process("測試資料")
    

    結論



    Python 使用抽象類別

    簡短回答

    在 Python 中,不一定需要 像 C++ 或 Java 那樣寫成 abstract class。 Python 採用「鴨子型別 (Duck Typing)」,只要物件具有相同的方法名稱與行為,即可視為相容。

    鴨子型別範例

    
    class AClass:
        def process(self, data):
            print(f"AClass 處理: {data}")
    
    class BClass:
        def process(self, data):
            print(f"BClass 處理: {data}")
    
    def run(obj):
        obj.process("資料")  # 不需指定型別,只要有這個方法即可
    
    run(AClass())
    run(BClass())
    

    輸出結果

    
    AClass 處理: 資料
    BClass 處理: 資料
    

    使用抽象類別 (ABC) 的情境

    雖然 Python 不強制型別,但若想在團隊開發或大型專案中明確定義介面,可以使用 abc 模組。

    
    from abc import ABC, abstractmethod
    
    class BaseClass(ABC):
        @abstractmethod
        def process(self, data):
            pass
    
    class AClass(BaseClass):
        def process(self, data):
            print(f"AClass 處理: {data}")
    
    class BClass(BaseClass):
        def process(self, data):
            print(f"BClass 處理: {data}")
    
    # BaseClass() 會報錯,因為抽象方法未實作
    

    結論



    Python 模組

    定義

    在 Python 中,模組(Module) 是一個包含程式碼的檔案,通常以副檔名 .py 結尾。模組可以定義函式、類別、變數,也可以包含可執行的程式碼,方便重複使用與程式結構化。

    用途

    使用方式

    Python 透過 import 關鍵字來引入模組,例如:

    
    import math
    
    print(math.sqrt(16))  # 輸出 4.0
    

    自訂模組

    開發者可以自己建立模組。例如建立一個 mymodule.py

    
    # mymodule.py
    def greet(name):
        return f"Hello, {name}!"
    

    在另一個程式檔案中使用:

    
    import mymodule
    
    print(mymodule.greet("Alice"))
    

    常見內建模組



    Python 套件

    定義

    在 Python 中,套件(Package) 是一種組織模組的方式。套件是一個包含多個模組的目錄,透過層級化的結構讓程式更容易管理與維護。

    特徵

    基本範例

    建立一個名為 mypackage 的套件:

    
    mypackage/
    │── __init__.py
    │── module1.py
    └── module2.py
    

    module1.py 範例:

    
    def add(a, b):
        return a + b
    

    module2.py 範例:

    
    def multiply(a, b):
        return a * b
    

    使用套件

    
    import mypackage.module1
    import mypackage.module2
    
    print(mypackage.module1.add(2, 3))      # 輸出 5
    print(mypackage.module2.multiply(2, 3)) # 輸出 6
    

    從套件匯入特定項目

    
    from mypackage.module1 import add
    from mypackage.module2 import multiply
    
    print(add(10, 5))       # 輸出 15
    print(multiply(10, 5))  # 輸出 50
    

    常見用途



    取得當前套件名稱

    說明

    在 Python 中,若要取得目前模組所屬的套件名稱,可以使用特殊變數 __package__。這和 __module__ 取得當前模組名稱的概念類似。

    範例:模組與套件結構

    
    mypackage/
    │── __init__.py
    └── submodule.py
    

    submodule.py 內容:

    
    print("__name__:", __name__)
    print("__package__:", __package__)
    print("__module__:", __module__)
    

    執行結果

    若在其他程式中以 import mypackage.submodule 匯入,輸出大致如下:

    
    __name__: mypackage.submodule
    __package__: mypackage
    __module__: __main__
    

    解釋

    應用場景



    Python 套件版本

    檢查 Python 套件版本有兩種主要途徑:透過終端機(命令列)或在 Python 程式碼中執行。這能幫助你確認環境是否符合專案需求。


    1. 透過終端機檢查 (Command Line)

    這是最快速的方法,不需要進入 Python 互動環境。


    2. 在 Python 程式碼中檢查

    如果你需要在程式執行時判斷版本,可以使用以下兩種方式:


    3. 檢查方法對照表

    方法 指令 / 程式碼 適用情境
    Pip 指令 pip show 查看安裝路徑、作者、依賴關係等詳細資訊。
    Pip 清單 pip list 快速概覽目前環境中所有套件與版本。
    內部屬性 .__version__ 在腳本運行中進行邏輯判斷。
    Metadata version() 標準化且不需加載整個套件的檢查方式。

    4. 檢查 Python 自身版本

    有時候問題不在套件,而是在 Python 解釋器本身:


    5. 常見問題與提醒



    從類別與模組物件取得套件名稱

    類別物件

    在 Python 中,類別物件可以透過 cls.__module__ 找到定義該類別的模組名稱,然後再透過 sys.modules 取得模組物件,最後讀取其 __package__ 屬性以得到套件名稱。

    
    import sys
    
    # 假設在 mypackage.submodule 裡定義一個類別
    class MyClass:
        pass
    
    # 取得類別所屬模組名稱
    module_name = MyClass.__module__
    print("模組名稱:", module_name)
    
    # 取得模組物件
    mod = sys.modules[module_name]
    
    # 從模組物件取得套件名稱
    print("套件名稱:", mod.__package__)
    

    模組物件

    模組本身就是一個物件,可以直接存取其 __package__ 屬性。

    
    import math
    import mypackage.submodule as sub
    
    # math 是標準函式庫模組,沒有套件,所以 __package__ 為空字串
    print("math.__package__:", math.__package__)
    
    # 自訂套件的模組
    print("sub.__package__:", sub.__package__)
    

    執行結果示例

    
    模組名稱: mypackage.submodule
    套件名稱: mypackage
    math.__package__: 
    sub.__package__: mypackage
    

    結論



    inspect.getfile

    當 Python 檔案被直接執行時,它的模組名就是 __main__。這使得僅使用 cls.__module__ 無法獲取原始的檔案名和路徑。

    假設您的專案結構是:

    /project
      |-- test_runner.py  <-- 您直接執行的檔案 (會被視為 __main__)
      |-- test/
            |-- db_test.py <-- 定義了 DbCmdAgent 的檔案

    如果您在 `test_runner.py` 中執行以下程式碼:

    from test.db_test import DbCmdAgent 
    agent_obj = DbCmdAgent(...) # 實例化
    
    # 此時 cls.__module__ 仍然是 'test.db_test' (正確的模組名)
    

    但是,如果您在 `test_runner.py` 中定義了類別:

    # test_runner.py 的內容
    class DbCmdAgent:
        pass
    
    agent_obj = DbCmdAgent()
    # 此時 cls.__module__ == '__main__' (錯誤的模組名)
    

    這表明您正在獲取資訊的類別是在被執行為 `__main__` 的檔案中定義的。

    使用 inspect 模組可直接獲取檔案路徑

    無論類別是否在 `__main__` 中定義,您都可以繞過 `__module__` 屬性,直接使用 inspect 模組來獲取該類別對應的原始程式碼檔案路徑。這是更可靠且更通用的方法。

    Python 修正實作

    import inspect
    import os
    
    # --- 模擬情境:類別在 __main__ (當前執行腳本) 中定義 ---
    
    class DbCmdAgent:
        """這個類別在當前執行的主腳本中定義"""
        def __init__(self, data):
            self.data = data
    
    agent_obj = DbCmdAgent("some_data")
    
    def get_class_location_robust(obj):
        """
        使用 inspect.getfile 繞過 __module__ == '__main__' 的問題。
        """
        cls = type(obj)
        
        # 1. 使用 inspect.getfile() 獲取定義該類別的檔案路徑
        try:
            file_path = inspect.getfile(cls) 
            
            # 2. 獲取檔案名和目錄
            file_name = os.path.basename(file_path)
            directory = os.path.dirname(file_path)
            py_name = os.path.splitext(file_name)[0]
            
            # 3. 如果 __module__ 是 __main__,則用檔案名替換它,以提供更多上下文
            module_name = cls.__module__
            if module_name == '__main__':
                module_name = py_name # 使用 db_test 或 test_runner 作為上下文
                
        except TypeError:
            # 處理內建類型
            file_path = "N/A (Built-in or C extension)"
            file_name = "N/A"
            directory = "N/A"
            py_name = "N/A"
            module_name = cls.__module__
            
        return {
            "module_name_or_main": module_name,
            "py_name_no_ext": py_name, 
            "directory": directory,
            "file_path": file_path,
        }
    
    # 執行並查看結果
    location_info = get_class_location_robust(agent_obj)
    
    print("--- 類別定義檔案資訊 ---")
    print(f"檔案名稱 (.py 名):  {location_info['py_name_no_ext']}")
    print(f"目錄路徑 (Package):  {location_info['directory']}")
    print(f"完整檔案路徑:        {location_info['file_path']}")
    

    4. 關鍵點總結



    動態匯入模組

    功能說明

    此方法利用 importlib.import_module 嘗試匯入指定模組,若遇到 ModuleNotFoundError,會從目前已載入的套件中再嘗試匯入其子模組。

    程式範例

    
    import importlib
    import sys
    
    def safe_import(module_name):
        try:
            # 直接嘗試匯入
            return importlib.import_module(module_name)
        except ModuleNotFoundError:
            # 若失敗,嘗試從已知套件中匯入子模組
            for pkg in list(sys.modules.keys()):
                if pkg and not pkg.startswith("_"):
                    try:
                        return importlib.import_module(f"{pkg}.{module_name}")
                    except ModuleNotFoundError:
                        continue
            raise  # 若仍找不到,拋出例外
    

    使用範例

    
    import numpy
    
    mod1 = safe_import("random")   # 可直接成功,因為是標準庫
    mod2 = safe_import("linalg")   # 會嘗試 numpy.linalg
    print(mod2.__name__)           # 輸出 numpy.linalg
    

    說明



    Python 模組搜索路徑:sys.path 的組成

    在 Python 中,sys.path 是一個列表(list),它包含了 Python 解譯器在嘗試匯入(import)模組時會依序搜索的所有目錄路徑。當您執行 import some_module 時,Python 會按順序檢查 sys.path 列表中的每個目錄,直到找到名為 some_module 的檔案(例如 some_module.pysome_module/__init__.py 等)。

    sys.path 的三個主要組成部分

    sys.path 列表通常由以下三個部分組成,並按以下順序搜索:

    1. 程式碼的入口目錄 (入口點)

    2. PYTHONPATH 環境變數

    3. 標準函式庫和安裝目錄

    ---

    與 sys.path 相關的系統變數

    除了上述的 PYTHONPATH 之外,還有幾個與 Python 執行環境相關的環境變數,它們會影響解譯器的行為和路徑查找,但影響 sys.path 組成的主要變數是 PYTHONPATH

    系統變數 功能描述 與 sys.path 的關係
    PYTHONPATH 定義額外要添加到模組搜索路徑的目錄。 直接影響 sys.path 的組成。
    PYTHONHOME 用於設定 Python 安裝目錄的替代路徑,特別是用於嵌入式系統。 間接影響標準函式庫和 site-packages 的位置。
    PATH 作業系統用來查找可執行文件(例如 python.exe)的路徑。 不直接影響 sys.path,但影響哪個 Python 解譯器被執行。
    VIRTUAL_ENV 當您處於虛擬環境中時,此變數指向虛擬環境的根目錄。 間接影響 sys.path,因為它確保 site-packages 是來自該虛擬環境而非系統全域的。

    如何修改 sys.path

    由於 sys.path 是一個普通的 Python 列表,您可以在程式運行時動態修改它,但這種修改只在當前解譯器會話中有效:

    import sys
    import os
    
    # 將父目錄添加到搜索路徑中 (常用於測試或專案內部引用)
    sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))


    模組陰影

    模組陰影(Module Shadowing,或稱 Name Shadowing)是 Python 中常見的一種錯誤或程式設計問題。它發生在當您自己建立的程式碼檔案或變數名稱,不小心與您嘗試匯入或使用的內建模組或第三方函式庫的名稱相衝突時。

    1. 定義與發生原因

    這導致的結果是:您本地的檔案「遮蓋」或「覆蓋」了原本應該載入的標準模組,使得您程式碼中對標準模組功能(例如 socket.AF_UNSPEC)的呼叫會失敗,因為您載入的本地檔案中並沒有這些屬性。

    2. 常見範例

    標準模組名 導致陰影的本地檔案名 後果
    socket socket.py 無法使用標準 socket 模組的網路常數(如 AF_INET, AI_PASSIVE)。
    json json.py 無法使用標準 json 模組的 loadsdumps 函式。
    test(專案名) test.py 在單元測試環境中,可能會與測試框架的內部邏輯衝突。

    3. 解決方案

    1. 重新命名檔案:這是最簡單和最有效的解決方案。只需將與標準模組或函式庫名稱衝突的本地檔案或目錄重新命名即可。例如,將 socket.py 改名為 network_handler.py
    2. 檢查 sys.path:在您的程式碼中執行 import sys; print(sys.path) 來查看 Python 的搜索路徑,以確認它是否優先於標準函式庫目錄載入了您的本地檔案。
    3. 使用虛擬環境:雖然虛擬環境本身不能防止本地檔案陰影,但它可以確保您安裝的第三方套件不會與其他環境衝突。


    取得目前入口 .py 名稱

    說明

    要取得 Python 程式中目前執行的 __main__ 所屬 .py 檔名稱,可使用 __main__.__file__sys.argv[0]。但在互動模式、Jupyter、或以 -c 執行時可能不存在,因此需要安全處理。

    取得目前 main .py 檔名(安全版本)

    import os
    import sys
    import __main__
    
    def get_main_py_path():
        """回傳 main 所屬 .py 絕對路徑,找不到時回傳 None"""
    
        # 情況 1:正常執行 .py
        main_file = getattr(__main__, "__file__", None)
        if main_file:
            return os.path.abspath(main_file)
    
        # 情況 2:從 sys.argv[0] 判斷
        if len(sys.argv) > 0:
            argv0 = sys.argv[0]
            if argv0 not in ("", "-c", ""):
                candidate = os.path.abspath(argv0)
                if os.path.exists(candidate):
                    return candidate
    
        # 情況 3:互動模式、Jupyter、embed etc.
        return None
    
    # 範例
    path = get_main_py_path()
    if path:
        print("main path:", path)
        print("main filename:", os.path.basename(path))
    else:
        print("找不到 main .py(可能在互動環境或不是從檔案執行)")

    只要檔名(basename)

    path = get_main_py_path()
    filename = os.path.basename(path) if path else None
    print(filename)

    總結



    拿到目前函式的參數名稱與值

    To get the parameter names and their corresponding values of a function in Python, you can use the `inspect` module, which provides introspection utilities. Specifically, `inspect.signature()` can help you retrieve the names of the parameters, and you can pass the current frame's local variables to get their values.
    
    Here is an example that demonstrates how to get the function name, parameter names, and their values:
    
    ```python
    import inspect
    
    # Sample function
    def my_function(a, b, c=5):
        # Get the current frame
        frame = inspect.currentframe()
        
        # Get the function name
        func_name = frame.f_code.co_name
        print(f"Function name: {func_name}")
        
        # Get the parameter names and their values
        args, _, _, values = inspect.getargvalues(frame)
        
        # Print parameter names and values
        for arg in args:
            print(f"Parameter name: {arg}, Value: {values[arg]}")
    
    # Call the function
    my_function(1, 2)
    ```
    
    ### Output:
    ```
    Function name: my_function
    Parameter name: a, Value: 1
    Parameter name: b, Value: 2
    Parameter name: c, Value: 5
    ```
    
    ### Explanation:
    1. **`inspect.currentframe()`**: Retrieves the current execution frame.
    2. **`frame.f_code.co_name`**: Extracts the name of the current function.
    3. **`inspect.getargvalues(frame)`**: Gets the argument names and their corresponding values from the frame. This function returns a tuple containing:
       - `args`: List of argument names.
       - `_`: Placeholder for unused information.
       - `values`: Dictionary containing argument names as keys and their values.
    
    This allows you to print both the names of the function's parameters and their values at runtime.
    


    取得函式參數的型別

    使用 inspect 模組

    在 Python 中,可以透過 inspect.signature() 取得函式的參數資訊, 並進一步從 Parameter.annotation 屬性獲得每個參數的型別註解(type hint)。

    
    import inspect
    
    def my_function(a: int, b: str, c: float = 3.14) -> bool:
        return str(a) == b
    
    sig = inspect.signature(my_function)
    
    for name, param in sig.parameters.items():
        print(f"參數名稱: {name}")
        print(f"  預設值: {param.default}")
        print(f"  型別註解: {param.annotation}")
        print()
    

    輸出結果

    
    參數名稱: a
      預設值: <class 'inspect._empty'>
      型別註解: <class 'int'>
    
    參數名稱: b
      預設值: <class 'inspect._empty'>
      型別註解: <class 'str'>
    
    參數名稱: c
      預設值: 3.14
      型別註解: <class 'float'>
    

    說明

    使用 get_type_hints

    此方法會自動解析 forward reference(以字串標註的型別)。

    
    from typing import get_type_hints
    
    hints = get_type_hints(my_function)
    print(hints)
    

    輸出結果

    
    {'a': <class 'int'>, 'b': <class 'str'>, 'c': <class 'float'>, 'return': <class 'bool'>}
    

    總結



    取得物件的類別名稱

    說明

    在 Python 中,可以透過物件的 __class__ 屬性或 type() 函式, 取得其所屬的類別(class),進而獲得類別名稱。

    範例

    
    class Animal:
        pass
    
    class Dog(Animal):
        pass
    
    obj = Dog()
    
    # 方法一:使用 __class__.__name__
    print(obj.__class__.__name__)   # 輸出: Dog
    
    # 方法二:使用 type()
    print(type(obj).__name__)       # 輸出: Dog
    
    # 方法三:取得完整模組與類別名稱
    print(obj.__class__)            # 輸出: <class '__main__.Dog'>
    print(obj.__class__.__module__) # 輸出: __main__
    

    輸出結果

    
    Dog
    Dog
    <class '__main__.Dog'>
    __main__
    

    說明

    進階應用

    若要同時取得完整的「模組 + 類別名稱」,可以這樣寫:

    
    cls = type(obj)
    full_name = f"{cls.__module__}.{cls.__name__}"
    print(full_name)
    

    輸出結果

    
    __main__.Dog
    

    總結



    檢測屬性所屬類別

    以下是使用 Python 判斷屬性屬於哪個繼承類別的範例程式碼:

    範例程式碼

    
    import inspect
    
    class BaseClass:
        base_attr = "我是來自 BaseClass 的屬性"
    
    class SubClass(BaseClass):
        sub_attr = "我是來自 SubClass 的屬性"
    
    # 定義函式以找出屬性歸屬的類別
    def find_attribute_owner(cls, attr_name):
        for base in inspect.getmro(cls):  # 取得 MRO(方法解析順序)
            if attr_name in base.__dict__:
                return base
        return None
    
    # 測試
    sub_obj = SubClass()
    attributes = sub_obj.__class__.__dict__.items()  # 取得類別層級的所有屬性
    for name, value in attributes:
        owner = find_attribute_owner(sub_obj.__class__, name)
        print(f"屬性 '{name}' 屬於類別: {owner.__name__}")
        

    程式說明

    執行結果

    對於範例中的類別,執行結果如下:

    
    屬性 '__module__' 屬於類別: SubClass
    屬性 'sub_attr' 屬於類別: SubClass
    屬性 '__doc__' 屬於類別: SubClass
    屬性 'base_attr' 屬於類別: BaseClass
        


    取得函式的註解 (Docstring)

    使用 __doc__

    class MyClass:
        def fun1(self):
            '''
            Fun1 comment
            '''
            pass
    
    obj = MyClass()
    print(obj.fun1.__doc__)
    

    使用 inspect 模組

    import inspect
    
    class MyClass:
        def fun1(self):
            '''
            Fun1 comment
            '''
            pass
    
    print(inspect.getdoc(MyClass.fun1))
    


    函式標示為已棄用

    使用 warnings 模組

    在 Python 中,可使用內建的 warnings 模組,在執行時(而非編譯時)提醒使用者某個函式已被棄用,並建議新的替代方法。

    
    import warnings
    
    def old_function(x, y):
        warnings.warn(
            "函式 old_function() 已棄用,請改用 new_function(x, y)。",
            category=DeprecationWarning,
            stacklevel=2
        )
        return x + y
    
    def new_function(x, y):
        return x + y
    

    執行範例

    
    result = old_function(3, 4)
    print(result)
    

    輸出結果

    
    DeprecationWarning: 函式 old_function() 已棄用,請改用 new_function(x, y)。
      result = old_function(3, 4)
    7
    

    補充說明

    進階:建立裝飾器自動標示棄用函式

    可用裝飾器(decorator)讓多個舊函式共用相同棄用提示邏輯:

    
    import warnings
    from functools import wraps
    
    def deprecated(new_func_name):
        def decorator(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                warnings.warn(
                    f"函式 {func.__name__}() 已棄用,請改用 {new_func_name}()。",
                    category=DeprecationWarning,
                    stacklevel=2
                )
                return func(*args, **kwargs)
            return wrapper
        return decorator
    
    @deprecated("new_function")
    def old_function(x, y):
        return x + y
    


    @staticmethod 與 @classmethod 的差異

    在 Python 中,@staticmethod@classmethod 這兩個裝飾器都可以定義不需要實例化類別就能調用的方法,但它們的用途和行為有所不同。

    @staticmethod

    @staticmethod 範例:

    class MyClass:
        @staticmethod
        def static_method(x, y):
            return x + y
    
    # 無需建立實例即可調用靜態方法
    result = MyClass.static_method(5, 10)  # 結果:15
        

    重點@staticmethod 無法訪問類別(cls)或實例(self)。

    @classmethod

    @classmethod 範例:

    class MyClass:
        class_variable = 0
    
        def __init__(self, value):
            self.value = value
            MyClass.class_variable += 1
    
        @classmethod
        def get_class_variable(cls):
            return cls.class_variable
    
    # 創建實例
    obj1 = MyClass(10)
    obj2 = MyClass(20)
    
    # 調用類別方法
    print(MyClass.get_class_variable())  # 結果:2
        

    重點@classmethod 可以訪問類別層級的狀態(cls)。

    總結

    特徵 @staticmethod @classmethod
    第一個參數 無隱含的第一個參數 cls(類別本身)
    訪問實例
    訪問類別
    用法 與類別相關但不需要實例或類別的工具函數 需要操作類別層級的數據或提供替代構造函數


    靜態類別執行初始化

    Python 本身並沒有提供「默認靜態方法」或「默認類別方法」,即在第一次調用任何靜態或類別方法時自動執行一個方法的功能。但我們可以通過懶加載技巧來實現類似的行為。

    解決方案:使用靜態變數和懶加載

    可以在類中定義一個靜態變數來追蹤初始化的狀態,然後在第一次調用靜態或類別方法時執行初始化邏輯。

    範例:

    class MyClass:
        initialized = False  # 靜態變數,跟蹤是否已經初始化
    
        @staticmethod
        def init_once():
            if not MyClass.initialized:
                print("初始化邏輯執行...")
                MyClass.initialized = True
    
        @classmethod
        def class_method(cls):
            cls.init_once()
            print("調用類別方法")
    
        @staticmethod
        def static_method():
            MyClass.init_once()
            print("調用靜態方法")
    
    # 第一次調用類別方法,觸發初始化
    MyClass.class_method()  # 輸出: 初始化邏輯執行... 調用類別方法
    
    # 第二次調用類別方法,不再執行初始化
    MyClass.class_method()  # 輸出: 調用類別方法
    
    # 第一次調用靜態方法,不再執行初始化,因為已經初始化過
    MyClass.static_method()  # 輸出: 調用靜態方法
        

    工作原理:

    總結

    雖然 Python 沒有內建「默認靜態方法」或「默認類別方法」,但通過使用靜態變數與懶加載技巧,你可以在第一次調用靜態或類別方法時自動執行初始化邏輯,並確保該邏輯只會被執行一次。



    Python 執行緒

    在 Python 中,執行緒(Thread)是用於實現並行(Concurrency)的一種機制。它允許程式在單一進程(Process)內同時執行多個任務。這對於執行 I/O 密集型操作(例如網路通訊、檔案讀寫)非常有用,可以避免程式因等待外部操作完成而阻塞(Blocking)。

    1. 為什麼使用執行緒?

    2. Python 執行緒的限制:GIL (全域解譯器鎖)

    在標準的 CPython 解譯器中,存在一個「全域解譯器鎖」(Global Interpreter Lock, GIL)。GIL 確保在任何給定時間,只有一個執行緒可以執行 Python 位元碼。這意味著:

    3. 執行緒模組:threading

    Python 使用標準函式庫中的 threading 模組來處理執行緒。有兩種主要的執行緒創建方法:

    方法一:傳遞函式作為目標 (Target Function)

    這是最簡單和最常見的用法。

    import threading
    import time
    
    def task(name, delay):
        """執行緒要執行的任務函式"""
        print(f"執行緒 {name}: 正在啟動...")
        time.sleep(delay) # 模擬耗時的 I/O 操作
        print(f"執行緒 {name}: 任務完成。")
    
    # 創建執行緒
    thread1 = threading.Thread(target=task, args=("T1", 2))
    thread2 = threading.Thread(target=task, args=("T2", 4))
    
    # 啟動執行緒
    thread1.start()
    thread2.start()
    
    # 等待所有執行緒完成(阻塞主執行緒直到它們結束)
    thread1.join()
    thread2.join()
    
    print("所有執行緒已完成。主程式退出。")

    方法二:繼承 threading.Thread 類別

    適用於更複雜的場景,將執行緒的邏輯封裝在一個類別中。

    import threading
    import time
    
    class MyThread(threading.Thread):
        def __init__(self, name, delay):
            super().__init__()
            self.name = name
            self.delay = delay
    
        def run(self):
            """
            當執行緒啟動時,會自動呼叫 run() 方法。
            在這裡定義執行緒要執行的任務。
            """
            print(f"執行緒 {self.name}: 正在啟動...")
            time.sleep(self.delay)
            print(f"執行緒 {self.name}: 任務完成。")
    
    # 創建並啟動執行緒
    thread3 = MyThread("T3", 3)
    thread3.start()
    thread3.join()
    
    print("自定義執行緒已完成。")

    4. 執行緒同步與資料共享

    當多個執行緒存取和修改共享數據時,可能會產生競爭條件(Race Condition)。您需要使用同步機制來保護數據:

    使用 Lock 範例

    import threading
    
    # 共享資源
    counter = 0
    # 創建鎖
    lock = threading.Lock()
    
    def increment_counter():
        global counter
        # 獲取鎖,確保同一時間只有一個執行緒可以執行此區塊
        lock.acquire()
        try:
            # 競爭區段
            current_value = counter
            time.sleep(0.001) # 模擬切換
            counter = current_value + 1
        finally:
            # 釋放鎖
            lock.release()
    
    threads = []
    for i in range(100):
        t = threading.Thread(target=increment_counter)
        threads.append(t)
        t.start()
    
    for t in threads:
        t.join()
    
    print(f"最終計數器值: {counter}") # 如果沒有鎖,這個值可能不是 100


    Python 執行緒的啟動、同步與停止

    Python 的 threading 模組提供了建立和管理執行緒的功能,但由於作業系統的限制和設計哲學,Python **沒有提供一個安全、直接、強制性停止(Kill)外部執行緒**的方法。強制停止可能導致資源洩漏或資料損壞。

    因此,停止執行緒必須透過 **協同機制(Cooperative Mechanism)** 來實現,即讓執行緒自己檢查一個停止旗標並優雅地退出。

    1. 執行緒停止機制:使用旗標 (Flag)

    這是最安全和最推薦的執行緒停止方法。它要求執行緒在執行任務的迴圈中定期檢查一個外部變數(旗標)。

    Python 實作範例

    import threading
    import time
    
    # 共享的停止旗標
    stop_flag = threading.Event()
    
    def monitored_task(name, delay):
        """
        會定期檢查停止旗標的任務函式
        """
        print(f"執行緒 {name}: 正在啟動...")
        i = 0
        while not stop_flag.is_set(): # 檢查旗標是否被設定
            i += 1
            print(f"執行緒 {name}: 執行步驟 {i}")
            
            # 模擬耗時操作,並定期檢查
            time.sleep(delay)
            
            # 在這裡可以設置一個執行次數限制,以確保不會無限循環
            if i >= 5:
                break
                
        print(f"執行緒 {name}: 收到停止訊號或任務結束,優雅退出。")
    
    # --- 主程式控制區塊 ---
    
    # 創建執行緒
    worker_thread = threading.Thread(target=monitored_task, args=("Worker-1", 1))
    
    # 啟動執行緒
    worker_thread.start()
    
    print("\n主程式: 執行緒已啟動,等待 3 秒...\n")
    time.sleep(3) # 讓執行緒運行一段時間
    
    # 發出停止訊號
    print("\n主程式: 設定停止旗標...\n")
    stop_flag.set() # 設定 Event,讓 is_set() 返回 True
    
    # 等待執行緒優雅地完成並退出 (通常很快)
    worker_thread.join()
    
    print("\n主程式: 執行緒已安全停止並加入。程式退出。")

    2. 關鍵組件說明

    3. 其他停止機制 (不推薦強制停止)

    儘管存在一些實驗性或不安全的強制停止方法,例如使用 `_thread.stop()` 或拋出異常,但這些方法都可能導致:

    因此,在 Python 中,始終應堅持使用協同的旗標機制來停止執行緒。



    多執行緒共享物件衝突的解決方案

    1. 最常用的解決方案:執行緒區域儲存 (Thread-Local Storage)

    這是在多執行緒環境中最推薦的做法。與其讓所有執行緒去搶同一個物件,不如讓每個執行緒都擁有該物件的一個獨立副本。在 Python 中,可以使用 threading.local() 來達成。

    import threading
    
    # 建立一個執行緒區域儲存物件
    thread_data = threading.local()
    
    def get_service():
        # 如果當前執行緒還沒有自己的 service,就建立一個
        if not hasattr(thread_data, 'service'):
            print(f"為執行緒 {threading.current_thread().name} 建立新連線")
            thread_data.service = create_new_connection() 
        return thread_data.service
    
    def task():
        service = get_service()
        # 執行操作...
    

    2. 其次常用的方法:鎖機制 (Locking)

    如果該物件必須是同一個(例如寫入同一個檔案或操作同一個全域計數器),則必須使用 Lock。這能保證同一時間只有一個執行緒能存取該物件,避免競爭條件(Race Condition)。

    lock = threading.Lock()
    
    def safe_task():
        with lock:
            # 在此區塊內,其他執行緒必須等待
            shared_object.do_something()
    
    ---

    替代多執行緒的方法:非同步與多進程

    如果您想避開多執行緒帶來的鎖競爭(Lock contention)或崩潰風險,可以考慮以下兩種主要的替代方案:

    1. 非同步協程 (Asyncio) - 適用於 I/O 密集型任務

    這是目前 Python 最流行的做法(例如 FastAPI 的核心原理)。它在單個執行緒內運行,透過切換任務來等待 I/O(如 API 請求、資料庫查詢)。

    import asyncio
    
    async def fetch_api(url):
        # 使用非同步庫如 aiohttp
        response = await call_api(url)
        return response
    
    async def main():
        # 同時執行多個任務,但在單執行緒內切換
        results = await asyncio.gather(fetch_api("url1"), fetch_api("url2"))
    

    2. 多進程 (Multiprocessing) - 適用於計算密集型任務

    Python 的執行緒受限於 GIL(全域解釋器鎖),無法真正平行運算。multiprocessing 會開啟多個獨立的 Python 解釋器實例。

    from multiprocessing import Process
    
    def task(name):
        print(f"進程 {name} 執行中")
    
    if __name__ == "__main__":
        p = Process(target=task, args=('A',))
        p.start()
        p.join()
    

    3. 任務佇列 (Task Queue) - 適用於分散式處理

    如果您希望將任務徹底解耦,可以使用 Celery 或 Redis Queue。將任務丟進佇列,由後端的 Worker(可能是多個進程或多台機器)去領取執行。

    ---

    總結建議

    方案 解決方式 適用情境
    Thread-Local 每個執行緒拿一份副本 API Service、資料庫連線
    Asyncio 單執行緒切換(非同步) 高併發網路請求 (推薦)
    Multiprocessing 獨立記憶體空間 CPU 運算、徹底避開共享衝突


    執行緒區域儲存與資訊共享的平衡

    核心概念

    當您使用執行緒區域儲存(Thread-Local Storage, TLS)時,目的是為了保護那些「非執行緒安全」的物件(如 API Service、資料庫連線)。但如果執行緒之間需要交換數據(例如 A 執行緒下載的結果要給 B 執行緒處理),您需要建立專門的「通訊管道」。

    1. 使用執行緒安全佇列 (Queue) - 最推薦

    Python 的 queue.Queue 是執行緒安全的。這是執行緒之間傳遞資訊最標準、最安全的方法。它內部已經處理好了所有鎖(Lock)的邏輯。

    import threading
    import queue
    
    # 建立一個全域佇列,所有執行緒都可以存取
    task_queue = queue.Queue()
    
    def producer():
        # 生產資料並放入佇列
        data = {"video_id": "abc", "status": "pending"}
        task_queue.put(data)
    
    def consumer():
        # 從佇列取出資料
        data = task_queue.get()
        # 處理資料...
        task_queue.task_done()
    

    2. 使用執行緒安全變數 (如共享清單加鎖)

    如果您需要共享的是一個大型列表或字典,您可以使用一般的全域變數,但存取時**必須**搭配 threading.Lock

    shared_results = []
    results_lock = threading.Lock()
    
    def task():
        result = "某些運算結果"
        
        # 存取共享資源前先上鎖
        with results_lock:
            shared_results.append(result)
        # 離開 with 區塊後自動解鎖
    

    3. 使用 Event 或 Condition 物件 (信號同步)

    有時候您不是要共享「資料」,而是要共享「狀態」(例如:告訴其他執行緒,API 已經初始化完成了)。

    api_ready = threading.Event()
    
    def initializer():
        # 執行初始化
        api_ready.set() # 發送訊號
    
    def worker():
        api_ready.wait() # 等待訊號,直到 initializer 呼叫 set()
        print("開始工作")
    

    總結:區域儲存 vs 共享資訊

    內容類型 存放位置 管理方式
    工具類物件 (API, DB 連線) Thread-Local (區域) 各自擁有副本,避免崩潰。
    任務資料 (ID, 參數) Queue (全域) 使用執行緒安全佇列傳遞。
    計算結果 (統計數據) Global List/Dict (全域) 必須搭配 threading.Lock

    簡單來說:**「私有的工具(連線)自己拿,公有的資料(數據)排隊領(Queue/Lock)。」**



    執行緒鎖

    基本概念

    在多執行緒環境中,當多個執行緒嘗試同時修改同一個全域變數或共享資源(如檔案、資料庫連線、全域清單)時,會發生競爭條件 (Race Condition),導致資料錯亂。threading.Lock 是一種同步原語,它確保同一時間只有一個執行緒可以進入受保護的程式碼區塊。

    1. 標準使用方法

    最安全且推薦的方式是搭配 with 敘述句使用。這能確保即使在區塊內發生異常(Exception),鎖也會被正確釋放,避免產生死鎖(Deadlock)。

    import threading
    
    # 1. 建立鎖物件
    my_lock = threading.Lock()
    shared_counter = 0
    
    def increment_task():
        global shared_counter
        # 2. 使用 with 自動管理 acquire() 與 release()
        with my_lock:
            # 此區塊內的程式碼同一時間只能有一個執行緒執行
            temp = shared_counter
            temp += 1
            shared_counter = temp
    
    # 啟動多個執行緒測試
    threads = [threading.Thread(target=increment_task) for _ in range(100)]
    for t in threads: t.start()
    for t in threads: t.join()
    
    print(f"最終計數: {shared_counter}")

    2. 手動控制方法

    雖然不推薦,但有時需要更精細的控制。您必須手動呼叫 acquire() 獲取鎖,並在 finally 區塊中呼叫 release()

    lock = threading.Lock()
    
    def manual_task():
        lock.acquire()  # 獲取鎖,若鎖已被占用則會在此阻塞(等待)
        try:
            # 執行任務
            pass
        finally:
            lock.release()  # 務必釋放,否則其他執行緒將永遠無法執行

    3. 鎖的特性:不可重入性

    threading.Lock 是不可重入的。這意味著如果同一個執行緒在已經持有鎖的情況下再次請求同一個鎖,它會把自己「鎖死」(死鎖)。

    4. 什麼時候該用 Lock?

    效能考量

    過度使用鎖會導致程式效能下降,因為多執行緒會變成「排隊執行」。如果可能,優先考慮使用 queue.Queue 或我們之前討論的 Thread-Local Storage,這些方法通常比頻繁上鎖更有效率且不易出錯。



    Python 非同步編程

    在 Python 中,async defawait 是實現非同步編程 (Asynchronous Programming) 的核心語法。它們能讓程式在等待 I/O 任務(如網路請求、讀取檔案)時不卡死,轉而處理其他任務,極大提升效能。


    1. async def:定義協程函數

    當你在函數定義前加上 async,該函數就會變成一個協程函數 (Coroutine Function)。呼叫它時,它不會立即執行內容,而是回傳一個「協程物件」。

    async def fetch_data():
        print("開始抓取資料...")
        # 模擬耗時任務
        return {"data": "success"}
    
    # 直接呼叫只會得到協程物件,不會執行 print
    result = fetch_data() 
    print(result) # 輸出: <coroutine object fetch_data at ...>
    

    2. await:掛起與等待

    await 只能在 async def 內部使用。它的作用是「暫時掛起目前的協程,等待後方的任務完成,並取得回傳值」。在等待期間,系統可以去執行其他的非同步任務。

    import asyncio
    
    async def main():
        # 使用 await 執行協程並取得結果
        data = await fetch_data() 
        print(f"抓取結果: {data}")
    
    # 啟動非同步程式的入口
    asyncio.run(main())
    

    3. 關鍵對照表

    語法 功能說明 注意事項
    async def 宣告一個非同步函數 回傳的是協程物件,非執行結果。
    await 等待非同步任務完成 只能寫在 async 函數內。
    asyncio.run() 啟動最外層的非同步入口 一個程式通常只需要呼叫一次。

    4. 常見錯誤與修正


    5. 為什麼要用非同步?

    想像你在煮飯:



    完成協程回傳結果

    loop.run_until_complete()asyncio 模組中較底層的方法,用於執行協程(Coroutine)直到其完成並回傳結果。在 Python 3.7 之後,雖然官方推薦使用 asyncio.run(),但在某些特定情境(如需要重複使用事件迴圈或自定義啟動邏輯)時,仍需使用此方法。

    1. 基本使用流程

    使用 run_until_complete 必須先獲取或建立一個事件迴圈物件,然後將協程傳遞給它。

    import asyncio
    
    async def my_task():
        await asyncio.sleep(1)
        return "任務完成"
    
    # 1. 獲取事件迴圈
    loop = asyncio.get_event_loop()
    
    # 2. 執行協程直到完成,並直接獲取 return 值
    result = loop.run_until_complete(my_task())
    
    print(result)  # 輸出: 任務完成
    

    2. 與 asyncio.run() 的差異

    這兩者都能得到回傳值,但管理生命週期的方式不同:

    特性 asyncio.run() (推薦) loop.run_until_complete()
    自動化程度 高。自動建立、關閉迴圈並清理任務。 低。需手動管理迴圈的生命週期。
    重複使用性 低。每次呼叫都會建立新迴圈。 高。可以在同一個迴圈執行多個任務。
    使用限制 不能在已運行的迴圈中使用。 較靈活,常用於舊版代碼或測試環境。

    3. 在現有迴圈中獲取結果

    如果你在一個已經在運行的腳本中,想要確保某個協程執行完畢並拿到值,可以使用此方法:

    import asyncio
    
    async def add(a, b):
        return a + b
    
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    
    try:
        # 執行第一個任務
        val1 = loop.run_until_complete(add(10, 20))
        # 執行第二個任務
        val2 = loop.run_until_complete(add(val1, 5))
        print(f"最終結果: {val2}")
    finally:
        # 必須手動關閉
        loop.close()
    

    4. 獲取多個任務的結果

    如果要讓多個協程並行執行並統一取回 return 值,需搭配 asyncio.gather

    async def task(id):
        return f"結果 {id}"
    
    loop = asyncio.get_event_loop()
    # gather 會將多個協程包裝成一個任務,run_until_complete 會等待全部完成
    results = loop.run_until_complete(asyncio.gather(task(1), task(2), task(3)))
    print(results)  # 輸出: ['結果 1', '結果 2', '結果 3']
    

    5. 常見注意事項



    asyncio.run() 是自 Python 3.7 之後引入的高階 API,它是啟動非同步程式最推薦的方式。它會自動負責建立事件迴圈、執行協程、並在完成後關閉迴圈。最重要的是,它會直接回傳協程中 return 的數值。

    1. 基本獲取方式

    你只需要將 async def 的函數呼叫傳遞給 asyncio.run(),它就會像一般同步函數一樣回傳結果。

    import asyncio
    
    async def calculate_score(name):
        print(f"正在計算 {name} 的分數...")
        await asyncio.sleep(1) # 模擬耗時任務
        return 95
    
    # 直接獲取 return 的結果
    final_score = asyncio.run(calculate_score("張小明"))
    
    print(f"最終分數是: {final_score}") # 輸出: 95
    

    2. 處理多個任務的結果

    通常我們會定義一個 main() 函數作為進入點,並在裡面獲取所有子任務的結果,最後由 asyncio.run(main()) 統一輸出。

    async def task_a():
        return "蘋果"
    
    async def task_b():
        return "香蕉"
    
    async def main():
        # 在 main 裡面使用 gather 同時執行
        results = await asyncio.gather(task_a(), task_b())
        return results # 回傳一個列表
    
    # 透過 asyncio.run 拿到 main 的回傳值
    all_fruits = asyncio.run(main())
    print(all_fruits) # 輸出: ['蘋果', '香蕉']
    

    3. asyncio.run() 的執行規則

    規則項目 說明
    單一入口 在一個執行續中,通常只呼叫一次 asyncio.run()
    自動清理 它會自動取消所有剩餘的任務並關閉執行緒池,非常安全。
    嵌套限制 不能在已經是 async def 的函數內部呼叫 asyncio.run()

    4. 常見報錯:RuntimeError

    如果你在異步函數內嘗試獲取另一個異步函數的結果,請使用 await,而不是 asyncio.run()

    # 錯誤示範
    async def sub_task():
        return 10
    
    async def main():
        # 這裡會噴錯:RuntimeError: asyncio.run() cannot be called from a running event loop
        res = asyncio.run(sub_task()) 
        
    # 正確修正
    async def main():
        res = await sub_task() # 在異步環境內請用 await
    

    5. 實戰建議



    Python 資料分析

    Python 科學運算 NumPy

    NumPy (Numerical Python) 是 Python 中最重要的科學運算程式庫。它提供了高效的多維陣列物件 ndarray,以及大量用於操作這些陣列的數學函式庫。它是數據科學、機器學習(如 Pandas, Scikit-learn, TensorFlow)等領域的底層支柱。


    1. 為什麼選擇 NumPy 而非原生 List?


    2. 核心物件:ndarray 基礎操作

    import numpy as np
    
    # 建立一維與二維陣列
    arr1 = np.array([1, 2, 3])
    arr2 = np.array([[1, 2], [3, 4]])
    
    # 快速建立特定陣列
    zeros = np.zeros((3, 3))    # 全為 0 的 3x3 矩陣
    ones = np.ones((2, 4))      # 全為 1 的 2x4 矩陣
    eye = np.eye(3)             # 3x3 單位矩陣
    range_arr = np.arange(0, 10, 2) # [0, 2, 4, 6, 8]

    3. 常用的陣列運算與屬性

    功能 程式碼範例 說明
    形狀檢查 arr.shape 回傳各維度的大小(如 (3, 2))。
    改變形狀 arr.reshape(1, 6) 在不改變數據的情況下變更維度。
    矩陣乘法 np.dot(a, b)a @ b 執行線性代數中的矩陣乘法。
    統計函數 np.mean(), np.std() 計算平均值、標準差、最大最小值。

    4. 切片與索引 (Slicing & Indexing)

    NumPy 的切片語法與 Python List 相似,但更強大,支援多維度同時切割:

    arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
    
    # 取得第二列 (index 1)
    print(arr[1, :]) # [4, 5, 6]
    
    # 取得右下角 2x2 子矩陣
    print(arr[1:, 1:]) # [[5, 6], [8, 9]]

    5. 安裝與版本檢查


    6. 配合 Numba 使用

    如先前提到,Numba 能夠完美識別 NumPy 的陣列結構,將複雜的 NumPy 運算進一步編譯為機器碼,達到接近原生 C 語言的執行極限。



    Python 高效能處理大量數據 Numba

    Numba 是一個開源的 JIT (Just-In-Time) 編譯器,專門設計用來加速處理大量數據的 Python 程式碼。它能將 Python 函數轉譯成機器碼,執行速度可與 C、C++ 或 Fortran 媲美,且特別適合用於 NumPy 陣列運算。


    1. 核心功能與優勢


    2. 基本使用範例

    使用 Numba 最簡單的方式就是加上 @jit@njit 裝飾器(Decorator)。

    from numba import njit
    import numpy as np
    
    # @njit 代表 "nopython" 模式,保證不進入 Python 解析器,速度最快
    @njit
    def fast_function(n):
        total = 0
        for i in range(n):
            total += i
        return total
    
    # 第一次呼叫會進行編譯,第二次呼叫則直接執行機器碼
    print(fast_function(10000000))

    3. 模式比較:nopython vs object

    模式 裝飾器 說明
    nopython 模式 @njit 推薦使用。 完全脫離 Python 直譯器,直接編譯為機器碼。若程式碼含無法編譯的部分會報錯。
    object 模式 @jit 若無法編譯,則退回到 Python 解析器執行。通常效能提升有限。

    4. 常用技巧:平行運算

    若要利用 CPU 的多核心能力,只需開啟 parallel=True 並使用 prange

    from numba import njit, prange
    
    @njit(parallel=True)
    def parallel_sum(A):
        s = 0
        # prange 會自動將迴圈分配到不同的 CPU 核心
        for i in prange(A.shape[0]):
            s += A[i]
        return s

    5. 安裝與依賴檢查


    6. 使用限制



    Pandas 資料分析工具

    什麼是 Pandas?

    Pandas 是一個基於 Python 的資料分析與操作工具,專門用於處理結構化數據,例如表格數據或時間序列數據。

    Pandas 的核心數據結構

    Pandas 的主要功能

    使用範例

    import pandas as pd
    
    # 建立 DataFrame
    data = {'姓名': ['Alice', 'Bob', 'Charlie'],
            '年齡': [25, 30, 35],
            '城市': ['台北', '台中', '高雄']}
    df = pd.DataFrame(data)
    
    # 查看數據
    print(df)
    
    # 篩選年齡大於 28 的資料
    filtered_df = df[df['年齡'] > 28]
    print(filtered_df)
        

    適用場景

    為什麼選擇 Pandas?

    Pandas 提供高效、靈活且直觀的操作方式,特別適合進行數據分析與處理,是數據科學和機器學習中不可或缺的工具之一。

    結論

    Pandas 是一個功能強大的資料分析工具,無論是入門還是高階使用者,都能受益於其簡單易用的設計和廣泛的功能。



    Python Googletrans

    安裝 Googletrans

    首先,您需要安裝 googletrans 套件。在命令列輸入以下指令:

    pip install googletrans==4.0.0-rc1

    注意:安裝時請確認版本是 4.0.0-rc1,因為較舊版本可能不再適用。

    基本使用範例

    以下是一個將英文翻譯成繁體中文的範例:

    
    from googletrans import Translator
    
    # 初始化 Translator 物件
    translator = Translator()
    
    # 翻譯文字
    text = "Hello, how are you?"
    result = translator.translate(text, src="en", dest="zh-tw")
    
    # 輸出翻譯結果
    print("原文:", text)
    print("翻譯:", result.text)
        

    支援的語言代碼

    您可以翻譯多種語言,以下是常見的語言代碼:

    注意事項

    Googletrans 是非官方的 Google 翻譯 API,因此有可能因為 Google 端的更改而停止運作。如果發現翻譯功能失效,請考慮使用其他翻譯 API,例如 Google 官方的 Cloud Translation API。



    Python 其他翻譯kit

    DeepL Translator

    DeepL 提供準確性較高的翻譯服務,但需要 API key 才能使用其開發者 API。

    Microsoft Translator

    由 Microsoft 提供的翻譯工具,支援多語言翻譯,但需使用 Azure 的 API key 設定。

    Amazon Translate

    Amazon Web Services (AWS) 提供的翻譯服務,針對多語言文本進行高效翻譯,需透過 AWS 提供的 API key 訪問。

    LibreTranslate

    LibreTranslate 是開源的翻譯工具,可自行架設服務器,不需要 API key。部分第三方公共伺服器也提供無需 API key 的使用選項。

    TextBlob

    TextBlob 是一個基於自然語言處理的工具,內建 Google Translate 的功能,但較舊版本的實現可無需 API key,可能需要注意版本支持。

    MyMemory

    MyMemory 提供基於記憶的翻譯,部分功能不需要 API key,但高級使用可能需申請。

    結論

    在 Googletrans 的競爭對手中,像 LibreTranslate 和部分版本的 TextBlob 提供了無需 API key 的選擇。如果需要完全免費且無需額外設定的工具,可考慮這些選項。



    OpenCC 中文轉換

    OpenCC (Open Chinese Convert) 是一個致力於中文簡繁轉換的開源專案。它不僅僅是簡單的字對字轉換,更重要的是它處理了詞彙級別的轉換以及不同地區(中國大陸、台灣、香港)的用字習慣差異。


    1. OpenCC 的核心優勢