分類: Python

  • Python:使用 API 與網頁爬蟲

    APIs 和網頁爬蟲讓我們能夠收集現實世界的數據進行分析。在這篇文章中,我希望談論一下基礎知識,

    • 使用 API 來獲取數據
    • 使用 BeautifulSoup 進行網頁爬取

    使用 API 來獲取數據

    API(應用程式介面)是一組規則和協議,允許不同的軟體應用程式相互通信。它充當橋樑,使一個系統能夠從另一個系統請求數據或服務,而無需了解其內部運作。例如,當您使用天氣應用程式時,它會從天氣提供者的 API 獲取即時數據,而不是自行存儲所有天氣數據。API 在數據分析中至關重要,因為它們允許分析師從各種來源訪問、檢索和整合即時或大規模數據,而無需手動下載。

    在我們最初的文章中,我們總是通過手動從網站下載或創建自己的數據集來處理 Excel 表格或 csv 文件。隨著我們不斷進步並開始處理大型數據集,這可能會變得有些不切實際。這就是為什麼我們使用 API 的原因。此外,手動下載的文件可能無法存儲實時或最新的數據。許多 API 提供最新或實時數據,這對於金融、天氣、股票市場或交通分析至關重要。API 還允許您高效地處理大型數據集,而無需將其存儲在本地。讓我們來看看數據分析中的一些 API,

    • 財務數據:Alpha Vantage API、Yahoo Finance API 用於股票市場分析
    • 社交媒體數據:用於情感分析的 Twitter API
    • 太空數據:NASA 用於太空任務數據的 API
    • 天氣數據:OpenWeather API 用於氣候研究

    現在我們知道了為什麼需要 API,讓我們來看看如何獲取數據並用於分析。大多數網站提供 API 來訪問結構化數據。在本文中,我們將使用 requests 庫來進行 API 請求。

    pip install requests

    我們現在要從一個公共 API(JSONPlaceholder API,這是一個免費的測試 API)中獲取數據。

    import requests
    
    url = "https://jsonplaceholder.typicode.com/users"
    response = requests.get(url) #fetch data
    if response.status_code == 200:
        data = response.json()  # Convert response to JSON format
        print(data[:2])  # Show first 2 users
    else:
        print("Error:", response.status_code)

    讓我們逐行查看這段代碼。

    • requests.get() 用於從網址獲取數據
    • status_code==200 是在檢查數據獲取是否成功。其他常見的狀態碼包括 404 表示未找到,以及 500 表示伺服器錯誤。
    • response.json() 將數據轉換為 JSON 格式。

    當你發出一個 API 請求時,回應通常是以 JSON 格式(JavaScript Object Notation)返回,這是一種輕量級的數據格式,易於閱讀和傳輸。然而,回應本身只是一個字串。.json()方法用於將這個字串解析為 Python 字典,使數據更易於處理。

    讓我們來看一下輸出。

    一些 API 需要 API 金鑰才能訪問。在這種情況下,你需要解析 API 金鑰來訪問數據。例如,從 OpenWeather API 獲取天氣數據。

    api_key = "your_api_key_here"  # Replace with your actual API key
    city = "London"
    url = f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={api_key}"
    
    response = requests.get(url)
    data = response.json()
    print(data)

    永遠記得保護好 API 金鑰,切勿在公開儲存庫中暴露它們。

    使用 BeautifulSoup 進行網頁抓取

    如果沒有可用的 API,我們可以從網頁中抓取數據。網路爬蟲是使用代碼自動從網站提取數據的過程。它涉及獲取網頁、解析其內容並提取有用信息。這可以使用 Python 中的 BeautifulSoup、Scrapy 和 Selenium 等工具來完成。網路爬蟲對於數據分析至關重要,因為它允許你從網路上收集大量可能無法通過 API 或數據庫獲得的數據。在本文中,我們將討論 BeautifulSoup。

    BeautifulSoup 是一個用於解析和提取 HTML 和 XML 文件數據的 Python 函式庫。它常用於網頁爬蟲中,以分析網頁內容並提取有用的資訊。

    lxml 是另一個用於處理 XML 和 HTML 的高效能函式庫。在處理大型 HTML 頁面時,它比 BeautifulSoup 更快。

    首先讓我們安裝兩個庫 BeautifulSoup 和 lxml。

    pip install beautifulsoup4 lxml

    現在讓我們試著使用這些庫來抓取一些數據。在這篇文章中,我將從一個維基百科頁面抓取標題。

    from bs4 import BeautifulSoup
    import requests
    import lxml
    
    url = "https://en.wikipedia.org/wiki/Web_scraping"
    response = requests.get(url)
    soup = BeautifulSoup(response.text, "lxml")  # Parse HTML
    headings = soup.find_all("h2")
    for h in headings:
        print(h.text.strip())
    • soup.find_all(“h2”) 找到所有的 <h2> 標籤
    • text.strip() 將提取並清理數據

    許多網站都有表格數據,例如股票價格。讓我們來看看如何從維基百科中抓取表格數據。

    from bs4 import BeautifulSoup
    import requests
    import lxml
    
    url = "https://en.wikipedia.org/wiki/Physics"
    response = requests.get(url)
    soup = BeautifulSoup(response.text, "lxml")
    # Find the first table on the page
    table = soup.find("table", {"class": "wikitable"})
    # Extract rows
    rows = table.find_all("tr")
    # Loop through rows & extract data
    for row in rows:
        cells = row.find_all(["th", "td"])  # Get table headers & data cells
        print([cell.text.strip() for cell in cells])

    如果你正在解析的 URL 包含任何表格,那麼這將會抓取頁面上的第一個表格。

    現在你可能在想,我們是否可以在任何網站上使用網頁爬蟲?

    答案是否定的。網頁爬取伴隨著法律和道德考量。你不能未經檢查網站政策就隨意爬取任何網站。那麼,你如何辨別是否允許網頁爬取呢?

    • 檢查網站的 robots.txt 文件(https://wikipedia.org/robots.txt)以查看是否允許爬取。
    • 閱讀服務條款並尋找有關數據使用的規則。
    • 你沒有損害網站,沒有讓其伺服器超載,也沒有違反其服務條款。
    • 如果網站有 API,這是首選的方法。
    • 在請求之間使用延遲以避免伺服器過載。在請求之間使用“time.sleep(1)”。

    總而言之,

    • 如果存在 API,那麼務必使用它。
    • 如果沒有 API 存在,那麼網頁抓取就是解決之道。但你不能從任何網站抓取數據。只使用允許網頁抓取的網站。
    • 檢查網站的 robots.txt 文件
    • 檢查網站的使用條款
    • 即使允許網頁爬取,也要注意道德考量。不要讓網站過載,在請求之間使用 time.sleep(1)

  • 如何使用 Python 進行探索性數據分析

    本文旨在了解 EDA、其在數據科學中的作用,並掌握統計分析、特徵選擇和可視化的關鍵技術。EDA 是數據分析的關鍵步驟。一旦收集了數據,下一步是清理和準備數據,並在進行複雜分析之前確定其有效性。因此,EDA 是數據分析的第一個也是最關鍵的步驟。它幫助我們

    • 了解數據集的結構和特徵。
    • 偵測遺漏值、離群值和異常值。
    • 識別模式、關係和分佈。
    • 決定機器學習模型的特徵選擇和工程化。

    了解您的數據

    資料分析的第一步是了解您的資料。讓我們從載入我們的資料集並檢查其資料結構開始。 就本文而言,我將使用 PyCharm,我的資料集是來自 Kaggle 的 Titanic 資料集 — https://www.kaggle.com/datasets/yasserh/titanic-dataset

    import pandas as pd
    
    df = pd.read_csv("Titanic-Dataset.csv")

    請注意,我正在處理的 CSV 檔案位於專案資料夾中。如果您的檔案位於其他位置,請輸入完整的檔案路徑。一旦我們載入了資料集,讓我們將其列印出來,以確認是否已正確載入。

    print(df.head())

    我正在使用 head()來只列印數據集的前 5 行,從中我可以檢查數據集是否已正確載入以及我的數據集中有什麼類型的列。

    查看欄位詳情

    print(df.info())

    我們可以看到欄位、它們的資料類型以及每個欄位有多少非空值。例如,在上述資料集中,共有 891 筆資料,但艙位欄位只有 204 筆資料。作為分析師,您可以決定是否考慮這些資料。Pandas 有另一個函數叫做 describe()非常有用,可以一目了然地獲得資料的更廣泛概念。

    print(df.describe())

    您可以查看每一列的計數、平均值、標準差、最小值和最大值等。一旦對數據有基本的了解,下一步就是清理數據集。

    數據清洗 — 處理缺失值

    早期我們發現該數據集存在缺失值,這是大多數數據集中的常見情況。在本節中,我們將重點關注如何查找和處理缺失數據。

    print(df.isnull().sum())

    本行為純文字輸入,翻譯至繁體中文。若翻譯不必要(如專有名詞、代碼等),請直接返回原文。 這是找出每個欄位中空白條目總數的最佳方法。這是我們泰坦尼克號數據集的輸出結果。

    我們可以看到大多數列沒有空值。這並不意味着這些列中的所有數據都有效或有用,這只是意味着這些列沒有空值。但是,Age、Cabin 和 Embarked 列有空值。您可以採取多種方式處理這一問題。如果這種做法會產生小影響,您可以刪除包含空值的行。或者,如果數據很重要,您可以填充空值。例如,在我們的數據集中,如果我們刪除包含 Cabin 列空值的 687 行,我們將只剩下 204 行數據。但如果我們的數據集有 50,000 個條目,也許影響會更小?這完全取決於您的數據集、其大小、我們想要刪除的數據類型以及對我們分析的總體影響。作為數據分析師,這些都是您必須做出的關鍵決策,這些決策最終將決定您的分析是否有效。

    為示範目的,讓我們假設我們決定丟棄 Embarked 欄包含空值的值,

    df = df.dropna(subset=['Embarked'])
    

    對於另外兩個欄位,我將以預設值填補缺失的值。對於「年齡」這個數值欄位,我將以資料集的平均年齡來填補缺失的值。

    df['Age'].fillna(df['Age'].mean(), inplace=True)

    小屋是一個字母數字列。對此,我將使用預設值”未知”。

    df['Cabin'].fillna('Unknown', inplace=True)

    讓我們再次運行 df.isnull().sum()來查看我們的工作是否成功。

    我們的數據集中現在沒有任何遺失的值。

    數據清洗 — 檢測和處理異常值

    統計學中,異常值是與其他觀察值有顯著差異的數據點。異常值可能由於多種原因而產生,例如新穎數據、測量變異性和實驗錯誤。如果確實是實驗錯誤,則應從分析中刪除該數據。但首先很重要的是確定您的數據集是否存在異常值,然後處理它們。

    辨認離群值的一種方法是使用箱形圖。如果您熟悉統計學,使用和解讀箱形圖將很容易。讓我們先建立一個非常簡單的箱形圖。

    sns.boxplot(x=df["Survived"])
    plt.show()

    倖存的欄位應該只有兩個值,要不就是 1,要不就是 0。讓我們現在檢查箱型圖吧。

    我們從這個圖表可以看出,沒有任何異常值。如果存在異常值,它們會以點的形式表示在盒子外部。以”Fare”列為例,

    我們可以看到這個圖表中有幾個異常值,也有一個極端異常值。是否保留或移除異常值取決於資料分析師的需求,不應盲目移除異常值,因為它們可能是真實的資料點。一旦確定了異常值並決定是否要移除它們,您可以使用四分位距 (IQR) 來執行該操作。當然,您也可以手動移除它們。

    統計學中,四分位距(IQR)被定義為數據第 75 百分位與第 25 百分位之間的差值。

    Q1 = df["Fare"].quantile(0.25)  
    Q3 = df["Fare"].quantile(0.75)  
    IQR = Q3 - Q1  
    df_filtered = df[(df["Fare"] >= (Q1 - 1.5 * IQR)) & (df["Fare"] <= (Q3 + 1.5 * IQR))]

    現在箱型圖看起來如何不同。因此,在去除異常值之前,非常重要要先思考這些是實驗錯誤還是真實數據點。

    了解數據分佈

    除了離群值之外,了解數據分布情況還可以窺探數據如何分佈,這有助於決定轉換、標準化等處理方式。首先,我將使用直方圖來視覺化數據分布。

    sns.histplot(df["Fare"], bins=30, kde=True)
    plt.show()

    請注意,我正在使用原始資料框架,未對票價欄應用四分位範圍,因此可以展示更大的分佈。

    我們可以看到最高數量的記錄位於 0 至 100 之間,並有少數介於 200 至 500 之間的離群值。另一種識別數據分佈的方法是檢查偏斜度。

    print(df["Fare"].skew())

    如果您獲得的值為>1 或<-1,則數據存在偏斜。因此,理想值應介於-1 和 1 之間。我對上述數據集獲得的值為 4.801440211044194。這意味著我的數據集存在偏斜。

    請記住我們在應用四分位距後的 df_filtered 資料集?我對該資料集應用了 skew() 函數,得到的值為 1.4306715336945985。該資料集仍然偏斜,但與先前版本相比,已不再極度偏斜。

    您可以使用對數轉換來修正偏斜數據。它通過取值的對數來修正偏斜,本質上是壓縮大值並拉伸小值,有助於平衡分佈。現在這完全取決於您正在處理的數據類型,因為它不適用於所有類型的數據。

    df["Fare"] = np.log1p(df["Fare"])

    在運行 df[“Fare”].skew()後,我得到了值 0.40010918935230094,這意味著我們的數據集現在適度歪斜。

    特徵關係和相關性

    特徵工程是 EDA 的重要組成部分。讓我們先看看數據中的特徵關係和相關性是什麼。數據中的特徵關係是指數據集中不同變量之間的連接或關聯,本質上描述了一個特徵的變化如何影響或與另一個特徵的變化相關。通常使用相關分析等統計方法來衡量變量之間關係的強度和方向。一個基本的例子就是一個包含學習時數和成績的數據集。

    根據我們的數據集,我們可以看到,學生學習的時間越長,在考試中獲得的成績就越高。這意味着學習時間和成績之間存在着關係。這就是我們所說的特徵關係和相關性。

    我們可以使用熱圖來識別您數據集中數值之間的相關性。

    print(df.corr(numeric_only=True))
    sns.heatmap(df.corr(), annot=True, cmap="coolwarm")
    plt.show()

    相關矩陣返回了我們數據集的以下輸出。我在應用任何 IQR 或對數轉換之前使用了該數據集。

    現在讓我們解釋這些結果。相關性的值從-1 到 1。

    • 正正正正正
    • 負 1 表示完美的負相關(一個增加,另一個減少)
    • 0 表示無相關性(特徵是獨立的)

    在先前的學生考試成績範例中,我們可以說,學習時數與考試成績呈現正相關,而缺席日數則可能與考試成績呈現負相關。有了這些認知,我們可以嘗試從上述熱圖中,找出資料集中任何變數間的特徵關係。

    讓我們進一步嘗試使用散佈圖來理解特徵之間的關係。從上面的熱力圖中我可以確定票價和乘客等級之間存在關係。

    sns.scatterplot(x=df["Fare"], y=df["Pclass"])
    plt.show()

    如果您想要檢查多個特徵,則我們可以使用對配圖。

    sns.pairplot(df)  
    plt.show()

    目前為止,我們已經探討了數值特徵之間的關係。現在讓我們來看看類別特徵。由於類別特徵並非數值型,因此我們需要以不同的方式分析它們。我們可以如下統計類別特徵的唯一值數量:

    print(df["Cabin"].value_counts())

    這將告訴我們船上的獨特艙房名稱。

    正如我們之前討論過的,我們通常使用條形圖來可視化類別數據。讓我們看看有多少男性和多少女性登上了這艘船。

    sns.countplot(x=df["Sex"])
    plt.show()

    乍看之下,我們可以看到船上乘客中男性比女性多。

    如果我們想探索更多,我們可以進一步了解機器學習中的 Python 庫,如 sklearn,學習特徵選擇和工程。我們可以選擇最重要的特徵進行分析,將分類變量轉換為數值以用於機器學習模型。我打算到此結束本文,希望能夠撰寫更多關於使用 Python 進行數據分析的文章,重點關注更高級的 Python 庫和機器學習技術。

  • 12 個我在工作中學到的生產級 Python 代碼風格

    在工作中,我們編寫的程式碼盡可能地易於人類閱讀。這意味著以下(非詳盡的)列表:

    • 變數名稱具有意義且較長(而非使用 a、b 和 c)
    • 函數名稱具有意義且較長
    • 大量的評論和文件解釋代碼
    • 類型提示無處不在
    • 字串似乎更長且更冗長
    • 等等等等

    這意味著代碼風格將不可避免地演變以適應上述情況。

    因此,以下是我在過去幾年的工作中學到的一些生產級 Python 代碼風格。

    1) 使用括號進行元組解包

    這是一些普通的元組解包:

    在生產級別的程式碼中,我們通常不會使用像 a 或 b 這樣的變數名稱——相反,我們的變數名稱會更長且更具描述性。

    因此,我們可能會使用括號來幫助元組解包,像這樣:

    注意到這樣一來,我們的元組解包可以容納更長(且更具描述性)的變數名稱

    一個更現實的例子:

    2) 多行列表推導式

    以下是普通列表推導式的樣子:

    在生產代碼中,我們通常不使用變量名 i——我們的變量通常更長且更具描述性,以至於我們無法將整個列表推導式塞進一行代碼中。

    我會如何重寫上述代碼:

    一個更現實的例子:

    3) 使用括號組合字串

    生產級的字串通常由於其冗長而無法放入一行中。因此,我們使用括號將它們組合起來。

    注意 — 在括號內,使用引號的字串字面值會自動合併,我們不需要使用 + 運算子來實現這一點

    4) 使用括號輔助的多行方法鏈接

      正常的方法鏈接:

    在生產級別的程式碼中,方法名稱大多數時候會更長,並且我們會將更多的方法鏈接在一起。

    再次,我們使用括號將所有這些內容放入多行中,而不是縮短任何方法名稱或變數名稱。

    請注意,如果我們在括號內進行方法鏈接,則不需要使用 \ 字符來進行明確的換行

    5) 索引嵌套字典

    正常索引嵌套字典的方式:

    這裡有一些問題:

    • 生產級代碼中的字典有更多層次的嵌套
    • 字典鍵的名稱變得更長了
    • 我們通常無法將整個嵌套索引代碼壓縮成一行。

    因此,我們將其拆分為多行,如下所示:

    如果這還不夠,我們將索引代碼拆分為更多行:

    或者如果我們仍然覺得這難以閱讀,我們可以這樣做:

    6) 撰寫易讀且資訊豐富的函數

    我們學生時代如何撰寫函式:

    包含此類代碼的 PR 很可能會被拒絕

    • 函數名稱不具有描述性
    • 參數變數名稱不佳
    • 沒有類型提示,所以我們第一眼無法知道每個參數應該是什麼數據類型
    • 沒有類型提示,所以我們也不知道我們的函數應該返回什麼
    • 沒有文件字串,所以我們必須推斷我們函數的功能

    以下是我們在生產級 Python 代碼中編寫函數的方式

    • 函數名稱應具有描述性
    • 參數名稱應該更具描述性,而不是例如 a, b, c
    • 每個參數都應該有類型提示
    • 函數的返回類型也應包括在內
    • 一個詳細描述函數功能、接收參數及其輸出的文檔字符串應作為三引號內的字符串包含。

    7) 盡可能減少縮排層級

    這是一個 for 迴圈。如果我們的條件被滿足,我們就做某事。

    一些同事和高級工程師可能會對這段代碼吹毛求疵 — 它可以通過減少縮進層次來寫得更好。

    讓我們重寫這個,同時減少 do_something() 的縮進層級

    注意到,僅僅通過使用 `if not condition` 而不是 `if condition`,`do_something()` 的縮進級別就減少了 1 級。

    在生產級別的程式碼中,可能會有更多的縮排層次,而如果層次太多,我們的程式碼就會變得令人煩躁且難以閱讀。因此,這個技巧讓我們能夠使程式碼稍微整潔一些,更易於人類閱讀,

    8) 帶有括號的布林條件

    這是一個使用 and 關鍵字連接三個條件的 if 語句。

    在生產級別的代碼中,條件變得更長,且可能會有更多的條件。因此,我們可以通過將這個龐大的條件重構為一個函數來解決這個問題。

    或者如果我們判斷沒有必要僅僅為此編寫一個新函數,我們可以使用括號來編寫我們的條件語句。

    這樣一來,我們就不必為了這一個條件語句而被迫編寫新的函數或變量,同時我們還能保持代碼的整潔和可讀性。

    有時候我可能真的更喜歡這樣寫,不過這只是基於個人偏好:

    9) 防止 None 值

    正常代碼,訪問對象的某些嵌套屬性。

    這段代碼存在一些可能導致我們的 PR 被拒絕的問題:

    • 如果 dog 是 None,我們會得到一個錯誤
    • 如果 dog.owner 是 None,我們也會得到一個錯誤
    • 基本上,這段程式碼並未防止 `dog` 或 `dog.owner` 可能為 `None` 的情況。

    在生產級代碼中,我們需要積極防範此類情況。以下是我如何重寫這段代碼的方式。

    Python 中的 and 和 or 運算符是短路運算符,這意味著一旦它們得到明確的答案,就會停止評估整個表達式。

    • 如果 dog 是 None,我們的表達式在 “if dog” 處終止
    • 如果 dog 不是 None,但 dog.owner 是 None,我們的表達式會在 “if dog and dog.owner” 處終止
    • 如果我們沒有任何 None 值,dog.owner.name 會成功被存取,並用於與字串 “bob” 進行比較

    這樣一來,我們就能額外防範狗或狗主人可能為 None 值的情況。

    10) 防止遍歷 None 值

    這是我們如何遍歷某些可迭代對象(例如列表、字典、元組等)的方式

    這個問題的一個缺點是它無法防止 mylist 為 None——如果 mylist 恰好是 None,我們會得到一個錯誤,因為無法遍歷 None。

    以下是我如何改進這段程式碼的方式:

    表達式“mylist or None”

    • 如果 mylist 為真值(例如非空可迭代對象),則返回 mylist
    • 如果 mylist 為假值(例如 None 或空的可迭代對象),則返回[]

    因此,如果 mylist 為 None,表達式“mylist or None”會返回[],這樣我們就不會遇到不想要的異常。

    11) 內部函數以 _ 開頭

    這是一個範例類別。在這裡,run 方法使用了其他方法 clean 和 transform

    在生產級代碼中,我們力求盡可能明確,因此嘗試區分內部方法和外部方法。

    • 外部方法——供其他類別和物件使用的方法
    • 內部方法 — 供類別本身使用的方法

    按照慣例,內部方法最好以下劃線 _ 開頭

    如果我們要重寫上述代碼,我們會得到:

    注意 — 在方法名稱前加上底線並不會隱藏它使其對其他類別和物件不可見。事實上,在功能上沒有任何區別。

    12) 用於常見功能的裝飾器

    這是一個包含 3 個函式的類別,每個函式執行不同的操作。然而,請注意,不同函式之間存在相似的步驟——try-except 區塊以及日誌功能。

    減少重複程式碼的一個好方法是撰寫一個包含共同功能的裝飾器函數。

    這樣一來,如果我們想要更新共用程式碼(try-except 和 logging 程式碼),我們不再需要在三個地方進行更新——我們只需要更新包含共用功能的裝飾器程式碼。

  • 在 Python 中進行除錯:將 print() 替換為 ic() 並像專業人士一樣操作

    在 Python 中進行除錯:將 print() 替換為 ic() 並像專業人士一樣操作

    介紹:

    幾個月前,當我在 Python 中進行一個數據分析專案時,我遇到了一個反覆出現的錯誤。儘管我多次檢查代碼,卻無法找出問題所在。像任何值得尊敬的程式設計師一樣,我使用了古老的 print() 來理解發生了什麼。然而,當我在終端中輸出了無數行結果後,我意識到這種方法有多麼低效。就在那時,我發現了 IceCream,這個庫幫助我大幅改善了調試過程,使其更乾淨、更有條理且專業。

    比較: print() vs ic()

    我們都曾使用 print() 來除錯程式碼,但這種技術有其局限性。雖然它易於實現,但在處理更複雜的函數和資料結構時,常常會變得混亂。這就是 IceCream 的用武之地,它提供了 ic() 函數,這是一個專門為除錯設計的工具,並具有額外的功能。

    基本範例 print() :

    def add(x, y):
        return x + y
    
    # Trying to debug with print()
    print(add(10, 20))  # Output: 30
    print(add(30, 40))  # Output: 70

    這種方法的問題在於,當輸出變得廣泛時,不清楚哪些值屬於每個結果。在這裡,您必須手動將每個結果與生成它的操作關聯起來。

    ic()相同的情況:

    from icecream import ic
    
    # Using ic() to debug
    ic(add(10, 20))
    ic(add(30, 40))

    輸出

    ic| add(10, 20): 30
    ic| add(30, 40): 70

    如你所見,IceCream 不僅會印出運算結果,還會顯示被呼叫的函數以及傳遞的參數。這大大簡化了除錯過程,尤其是當你有多個函數呼叫且輸出相似時。

    使用 ic() 的優勢

    1. 詳細操作資訊

    使用ic(),你不僅可以看到結果;你還可以看到執行的操作。這消除了使用f-strings或手動註解來了解正在打印內容的需求。

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

    輸出

    ic| multiply(5, 5): 25

    2. 同時進行除錯和賦值

    ic() 的另一個主要優勢是,它允許你在調試的同時進行賦值,這是 print() 無法做到的。

    # Using print()
    result = print(multiply(4, 6))  # Output: 24
    print(result)  # Output: None
    
    # Using ic()
    result = ic(multiply(4, 6))  # Output: ic| multiply(4, 6): 24
    print(result)  # Output: 24

    使用 ic(),你可以獲得計算的值並將其存儲在 result 變量中,這與 print() 不同,後者不會返回任何值。

    3. 存取資料結構

    當處理字典或列表時,ic() 變得更加有用。讓我們看看它在存取字典元素時是如何運作的:

    data = {'a': 1, 'b': 2, 'c': 3}
    
    # Using ic() to debug
    ic(data['a'])

    輸出

    ic| data['a']: 1

    再次,它清楚地顯示了哪個鍵被訪問以及返回了什麼值。

    4. 複雜結構的可見性更好

    當處理較大的資料結構時,例如巢狀字典或 JSON,ic() 提升了程式碼的可讀性,以更有組織的方式顯示結構:

    complex_data = {
        "name": "John",
        "age": 30,
        "languages": ["Python", "JavaScript"]
    }
    
    ic(complex_data)

    輸出內容以顏色和結構化格式呈現,使得理解和分析大型數據結構的內容變得更加容易。

    IceCream 的額外功能

    除了基本優勢外,IceCream 還提供了額外的功能,讓你能夠控制 ic() 函數的行為:

    暫時禁用 ic()

    如果某個時候你不想讓 ic() 在程式碼的某些部分輸出任何內容,你可以禁用它,稍後再重新啟用它:

    ic.disable()  # Disables ic()
    ic(multiply(3, 3))  # Prints nothing
    
    ic.enable()  # Re-enables ic()
    ic(multiply(3, 3))  # Output: ic| multiply(3, 3): 9

    配置輸出

    你可以自訂ic()的輸出,例如添加前綴或將輸出寫入檔案,而不是將其打印到終端。

    def log_to_file(text):
        with open("debug.log", "a") as f:
            f.write(text + "\n")
    
    ic.configureOutput(prefix="DEBUG| ", outputFunction=log_to_file)
    
    ic(multiply(7, 7))

    這將會把 ic() 的輸出發送到 debug.log 文件中,並加上前綴 "DEBUG| "

    結論

    使用print()進行除錯是一種常見的技巧,但它有其局限性。IceCream提供了一個更強大且專業的解決方案,用於在 Python 中進行除錯,提供了詳細的信息、靈活性以及更整潔的格式化。使用ic(),你可以在除錯時節省時間並提高代碼的可讀性。

  • 10 件你可能不知道會破壞 Python 腳本的事情

    Python 以其簡單性和可靠性而聞名,但即使是最優秀的開發者也會遇到神秘的錯誤和意外的錯誤。有些陷阱是顯而易見的,但其他的則潛伏在陰影中,等待破壞你的腳本。如果你曾經對代碼中的某個問題感到困惑,那麼這些隱藏的 Python 殺手之一很可能是罪魁禍首。

    這裡有 10 件你可能不知道會破壞你的 Python 腳本的事情——以及如何避免它們。

    1. 可變的預設參數

    問題:在函數定義中使用可變對象(如列表或字典)作為默認參數可能會導致意外行為。

    # Bad practice
    def add_item(item, items=[]):
        items.append(item)
        return items
    
    
    print(add_item(1))  # [1]
    print(add_item(2))  # [1, 2] – Wait, what?!

    修正方法:始終使用不可變的默認值,如 None,並在函數內部初始化。

    def add_item(item, items=None):
        if items is None:
            items = []
        items.append(item)
        return items

    2. 混合環境中的不正確縮排

    問題: 混合使用制表符和空格可能會導致難以發現的錯誤,特別是在協作項目時。

    # Mixing spaces and tabs
    if True:
    	print("This will fail in some environments!")

    修正方法: 使用一致的縮排風格 — 最好是空格。配置您的編輯器自動將制表符轉換為空格。

    3. 在迭代時修改列表

    問題: 在迴圈中更改列表可能會導致跳過元素和不可預測的行為。

    numbers = [1, 2, 3, 4, 5]
    for num in numbers:
        if num % 2 == 0:
            numbers.remove(num)  # Oops! Skips elements

    修正方法:遍歷列表的副本或使用列表推導式。

    numbers = [num for num in numbers if num % 2 != 0]

    4. 使用 is 而不是 == 進行比較

    問題: is 運算符檢查對象的身份,而不是相等性。

    x = 256
    y = 256
    print(x is y)  # True (because of interning)
    
    
    x = 300
    y = 300
    print(x is y)  # False!

    修正方法: 使用 == 進行值比較,並且僅在必要時使用 is 進行身份檢查。

    5. 預設字典鍵的意外行為

    問題: 在常規字典中訪問缺失的鍵會引發 KeyError,而 defaultdict 會自動創建缺失的鍵,有時會導致微妙的錯誤。

    from collections import defaultdict
    
    d = defaultdict(list)
    d["missing"].append(42)  # Key is created automatically!

    修正方法: 當您需要安全查找時,使用 dict.get()

    value = my_dict.get("missing", [])

    6. 迴圈變數洩漏到全域範圍

    問題:在 Python 2 中,循環變量會洩漏到循環外,導致意外的修改。

    for i in range(5):
        pass
    print(i)  # i is still accessible

    修正方法: 使用函數範圍來封裝變量,或使用 Python 3,這已經修復。

    7. 浮點數精度錯誤

    問題: 浮點運算可能導致微小的不準確性。

    print(0.1 + 0.2)  # 0.30000000000000004

    修正方法: 當精確度至關重要時,使用 decimal 模組。

    from decimal import Decimal
    print(Decimal("0.1") + Decimal("0.2"))  # 0.3

    8. 多執行緒中的競爭條件

    問題:同時訪問共享資源的線程可能會導致不可預測的行為。

    import threading
    
    def increment(counter):
        for _ in range(1000):
            counter["count"] += 1
    
    counter = {"count": 0}
    threads = [threading.Thread(target=increment, args=(counter,)) for _ in range(10)]
    
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    
    print(counter["count"])  # Not 10000!

    修正方法: 使用線程安全的結構,如 threading.Lock

    9. 循環導入

    問題:兩個模組互相導入可能會導致ImportError

    # module_a.py
    import module_b
    
    # module_b.py
    import module_a  # Circular import error!

    解決方案: 通過使用本地導入或重構代碼來打破依賴關係。

    10. 默默忽略例外

    問題: 捕捉所有異常而不正確處理會使調試變得困難。

    try:
        risky_function()
    except Exception:
        pass  # Silent failure!

    修正方法: 記錄例外並在可能的情況下處理特定的例外。

    import logging
    
    try:
        risky_function()
    except ValueError as e:
        logging.error(f"Value error: {e}")

    結論

    Python 是一種強大的語言,但即使是經驗豐富的開發人員也可能會陷入這些陷阱。通過了解這些隱藏的陷阱,您可以編寫更可靠、可維護且無錯誤的 Python 代碼。