Python 的 logging 模組提供彈性的日誌記錄功能,允許開發者透過組態檔案、字典或程式碼設定日誌輸出。設定日誌等級能控制訊息的詳細程度,格式化器定義輸出格式,處理器則決定訊息的輸出目的地。瞭解這些元件的互動作用,才能有效地追蹤程式行為、診斷錯誤。開源專案的授權選擇同樣關鍵,它影響程式碼的使用、修改和分發方式。從寬鬆式授權到 Copyleft 授權,不同的授權型別賦予使用者不同的權利和義務。選擇授權時,需考量專案目標、社群期望和與其他專案的相容性。

在軟體開發中,閱讀優秀的開源程式碼是精進技術的有效途徑。以 HowDoI 和 Diamond 等專案為例,可以學習如何組織程式碼結構、撰寫清晰的函式和處理異常。這些專案示範了單一職責原則、適當的註解和 Pythonic 的程式碼風格。此外,理解專案的授權條款和相容性問題,對於參與開源社群至關重要。藉由分析這些專案,開發者可以提升程式碼品質,並學習如何構建更健壯、可維護的軟體。

Python 日誌記錄與開源專案授權選擇

在軟體開發過程中,日誌記錄和專案授權是兩個至關重要的議題。本文將探討 Python 中的日誌記錄機制,並討論開源專案的授權選擇。

Python 日誌記錄機制

Python 提供了一個強大的日誌記錄模組 logging,可幫助開發者追蹤程式執行狀態、診斷問題。日誌記錄的組態可以透過多種方式實作,包括使用組態檔案、字典組態或直接在程式碼中設定。

使用組態檔案進行日誌記錄

首先,我們來看一個使用 INI 檔案組態日誌記錄的例子。以下是一個範例組態檔案 logging_config.ini 的內容:

[handler_streamHandler]
class=StreamHandler
level=DEBUG
formatter=formatter
args=(sys.stderr,)

[formatter_formatter]
format=%(asctime)s %(name)-12s %(levelname)-8s %(message)s

在程式碼中,我們可以使用 logging.config.fileConfig() 函式載入此組態檔案:

import logging
from logging.config import fileConfig

fileConfig('logging_config.ini')
logger = logging.getLogger()
logger.debug('often makes a very good meal of %s', 'visiting tourists')

使用字典進行日誌記錄組態

Python 2.7 之後,我們可以使用字典來組態日誌記錄。以下是一個範例:

import logging
from logging.config import dictConfig

logging_config = dict(
    version=1,
    formatters={
        'f': {'format': '%(asctime)s %(name)-12s %(levelname)-8s %(message)s'}
    },
    handlers={
        'h': {'class': 'logging.StreamHandler', 'formatter': 'f', 'level': logging.DEBUG}
    },
    loggers={
        'root': {'handlers': ['h'], 'level': logging.DEBUG}
    }
)

dictConfig(logging_config)
logger = logging.getLogger()
logger.debug('often makes a very good meal of %s', 'visiting tourists')

直接在程式碼中組態日誌記錄

我們也可以直接在程式碼中設定日誌記錄:

import logging

logger = logging.getLogger()
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
logger.debug('often makes a very good meal of %s', 'visiting tourists')

內容解密:

  1. 日誌記錄等級:上述範例均將日誌記錄等級設為 DEBUG,這意味著所有等級的日誌訊息(包括 DEBUGINFOWARNINGERRORCRITICAL)都會被記錄。
  2. 格式化器:格式化器定義了日誌訊息的格式。在上述範例中,格式字串 %(asctime)s %(name)-12s %(levelname)-8s %(message)s 表示每條日誌訊息將包含時間戳、記錄器名稱、日誌等級和實際訊息。
  3. 處理器:處理器決定了日誌訊息的輸出目的地。在上述範例中,使用了 StreamHandler 將日誌訊息輸出到標準錯誤流。

開源專案授權選擇

在發布開源專案時,選擇合適的授權協定至關重要。這決定了其他人如何使用、修改和分發你的程式碼。

上游授權的影響

如果你的專案衍生自另一個專案,你的授權選擇可能會受到上游專案授權的限制。例如,Python 軟體基金會(PSF)要求對 Python 原始碼的貢獻者簽署貢獻者協定,將程式碼授權給 PSF。

授權選項

有多種開源授權可供選擇。PSF 建議使用開源促進會(OSI)批准的授權。常見的開源授權包括:

  • 寬鬆式授權(如 Apache 2.0、BSD 和 MIT 授權):這些授權給予使用者很大的自由度,允許他們以各種方式使用、修改和分發軟體。
  • Copyleft 授權(如 GPL 家族):這些授權要求衍生作品也必須以相同的授權條款發布,確保原始碼及其修改始終開放。

選擇合適的授權

在選擇授權時,你需要考慮你的專案需求和目標。例如,如果你希望你的程式碼被廣泛採用,寬鬆式授權可能更合適。如果你希望確保衍生作品保持開放,Copyleft 授權可能是更好的選擇。

內容解密:

  1. 寬鬆式與 Copyleft 授權的區別:寬鬆式授權提供了更大的靈活性,而 Copyleft 授權則確保了原始碼的開放性。
  2. 相容性問題:不同的授權之間可能存在相容性問題,例如,Apache 2.0 授權的程式碼可以與 GPLv3 專案結合,但 GPLv2 與 Apache 2.0 不相容。
  3. 選擇建議:選擇 OSI 批准的授權,並考慮你的專案目標和社群期望。

綜上所述,無論是日誌記錄還是開源專案的授權選擇,都需要根據具體需求和目標進行仔細考慮。透過合理的組態和選擇,可以更好地實作專案目標並促進社群的發展。

閱讀優秀程式碼的技巧與實踐

在軟體開發的世界中,閱讀他人的程式碼是一項基本且重要的技能。Python 的設計哲學之一便是強調可讀性,而要成為一名優秀的程式設計師,閱讀、理解並學習優秀的程式碼是必經之路。本章節將透過幾個知名的 Python 專案來探討如何閱讀優秀的程式碼,並分享相關技巧。

精選專案介紹

本章節將介紹以下幾個 Python 專案:

  • HowDoI:一個根據命令列的應用程式,能夠搜尋網路上的程式設計問題答案。
  • Diamond:一個 Python 守護程式,用於收集系統指標並將其釋出到 Graphite 等後端。
  • Tablib:一個與格式無關的表格資料集函式庫。
  • Requests:一個為人類設計的 HTTP 函式庫,簡化了密碼驗證和多部分檔案上傳等操作。
  • Werkzeug:最初是一系列用於 Web Service Gateway Interface(WSGI)應用的工具集,現已發展成為最先進的 WSGI 實用模組之一。
  • Flask:一個根據 Werkzeug 和 Jinja2 的 Python 網頁微框架,適合快速建立簡單的網頁應用。

這些專案在程式碼可讀性方面都有出色的表現,以下是它們的一些共同特點:

共同特點分析

專案名稱授權條款程式碼行數檔案字串比例註解比例空白行比例平均函式長度
HowDoIMIT2620%6%20%13 行
DiamondMIT6,02121%9%16%11 行
TablibMIT1,80219%4%27%8 行
RequestsApache 2.04,07223%8%19%10 行
FlaskBSD 3-clause10,1637%12%11%13 行
WerkzeugBSD 3-clause25,82225%3%13%9 行

程式碼閱讀技巧

在閱讀這些專案的程式碼時,我們採用了不同的技巧來瞭解專案的功能和實作細節。以下是一些值得注意的觀察:

程式碼結構與註解

大多數專案都保持著良好的程式碼結構,函式長度適中,並且使用了適當的檔案字串和註解來解釋程式碼的功能。例如,Werkzeug 和 Requests 都使用了大量的檔案字串來說明函式和類別的用途。

程式碼可讀性

這些專案的程式碼都具有很高的可讀性。例如,Tablib 的程式碼結構清晰,易於理解。HowDoI 的程式碼則展現瞭如何在不使用檔案字串的情況下,透過簡潔明瞭的註解和程式碼來達到良好的可讀性。

常見的最佳實踐

  • 保持函式簡短:大多數函式的長度都控制在20行以內,這使得程式碼更容易理解和維護。
  • 適當使用空白行:適當的空白行可以提高程式碼的可讀性,使不同邏輯區塊之間的區隔更加明顯。
  • 使用檔案字串和註解:檔案字串和註解對於解釋程式碼的功能和邏輯至關重要,尤其是在複雜的專案中。

如何提升自己的程式碼閱讀能力

  1. 選擇優秀的開源專案:選擇一些知名的開源專案來閱讀,例如本章節介紹的幾個專案。
  2. 從簡單的專案開始:先從較小的專案開始,例如 HowDoI 或 Tablib,然後逐漸轉向更複雜的專案,如 Werkzeug 或 Flask。
  3. 關注程式碼結構和註解:在閱讀程式碼時,注意其結構、註解和檔案字串的使用。
  4. 實踐和分享:嘗試將學到的技巧和最佳實踐應用到自己的專案中,並與他人分享你的學習成果。

閱讀優秀程式碼:以HowDoI為例

從單一檔案指令碼開始閱讀

HowDoI是一個僅有不到300行程式碼的小型專案,卻是學習閱讀優秀程式碼的絕佳起點。相較於提供API或框架的函式庫,指令碼通常具有明確的起點、選項和終點,使其更容易被理解。

取得並安裝HowDoI

首先,從GitHub取得HowDoI模組:

$ git clone https://github.com/gleitz/howdoi.git
$ virtualenv -p python3 venv 
$ source venv/bin/activate
(venv)$ cd howdoi/
(venv)$ pip install --editable .
(venv)$ python test_howdoi.py 

內容解密:

  1. 使用git clone從GitHub下載HowDoI專案。
  2. 透過virtualenv建立虛擬環境並啟用。
  3. 切換到專案目錄並以可編輯模式安裝。
  4. 執行單元測試以驗證安裝正確性。

瞭解HowDoI的使用方法

執行howdoi --help可檢視使用說明:

(venv)$ howdoi --help
usage: howdoi [-h] [-p POS] [-a] [-l] [-c] [-n NUM_ANSWERS] [-C] [-v]
              [QUERY [QUERY ...]]
instant coding answers via the command line
positional arguments:
  QUERY                 the question to answer
optional arguments:
  -h, --help            show this help message and exit
  -p POS, --pos POS     select answer in specified position (default: 1)
  -a, --all             display the full text of the answer
  -l, --link            display only the answer link
  -c, --color           enable colorized output
  -n NUM_ANSWERS, --num-answers NUM_ANSWERS
                        number of answers to return
  -C, --clear-cache    clear the cache
  -v, --version         displays the current version of howdoi

內容解密:

  1. QUERY為必填引數,表示要查詢的問題。
  2. -p引數用於指定答案的位置,預設為1。
  3. -a引數顯示完整答案內容。
  4. -c引數啟用彩色輸出。
  5. -n引數指定傳回的答案數量。

實際使用HowDoI

執行範例:

(venv)$ howdoi --num-answers 2 python lambda function list comprehension
---
 Answer 1 
---
[(lambda x: x*x)(x) for x in range(10)]
---
 Answer 2 
---
[x() for x in [lambda m=m: m for m in [1,2,3]]]
# [1, 2, 3]

內容解密:

  1. 使用--num-answers 2引數要求傳回兩個答案。
  2. 第一個答案展示瞭如何在列表推導式中使用lambda函式。
  3. 第二個答案則演示了另一種使用lambda的列表推導式。

分析HowDoI的程式碼結構

HowDoI的主要邏輯位於howdoi.py檔案中。程式碼結構清晰,每個函式只執行單一任務。主要的函式呼叫流程如下:

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title Python日誌記錄與開源專案授權

package "安全架構" {
    package "網路安全" {
        component [防火牆] as firewall
        component [WAF] as waf
        component [DDoS 防護] as ddos
    }

    package "身份認證" {
        component [OAuth 2.0] as oauth
        component [JWT Token] as jwt
        component [MFA] as mfa
    }

    package "資料安全" {
        component [加密傳輸 TLS] as tls
        component [資料加密] as encrypt
        component [金鑰管理] as kms
    }

    package "監控審計" {
        component [日誌收集] as log
        component [威脅偵測] as threat
        component [合規審計] as audit
    }
}

firewall --> waf : 過濾流量
waf --> oauth : 驗證身份
oauth --> jwt : 簽發憑證
jwt --> tls : 加密傳輸
tls --> encrypt : 資料保護
log --> threat : 異常分析
threat --> audit : 報告生成

@enduml

此圖示展示了HowDoI的主要函式呼叫流程

內容解密:

  1. command_line_runner()解析輸入引數並呼叫howdoi()
  2. howdoi()包裝了_get_instructions(),處理連線錯誤。
  3. _get_instructions()呼叫_get_links()進行Google搜尋,並對每個連結呼叫_get_answer()
  4. _get_answer()提取Stack Overflow上的程式碼並進行彩色化處理。

HowDoI的包裝結構

setup.py檔案展示瞭如何為專案設定可執行的指令:

setup(
    name='howdoi',
    ##~~ ... Skip the other typical entries ...
    entry_points={
        'console_scripts': [
            'howdoi = howdoi.howdoi:command_line_runner',
        ]
    },
    ## ~~ ... Skip the list of dependencies ...
)

內容解密:

  1. entry_points關鍵字定義了可執行的指令。
  2. console_scripts清單中指定了howdoi指令對應到howdoi.howdoi.command_line_runner()函式。

結構範例與心得

HowDoI的程式碼結構展示了以下優秀實踐:

  1. 單一職責原則:每個函式只負責單一任務。
  2. 清晰的函式命名:函式名稱直接反映其功能。
  3. 結構化的錯誤處理:適當使用try/except處理特定錯誤。

這些特質使得HowDoI的程式碼易於理解和維護,為學習Python程式設計和專案結構提供了良好的範例。

如何寫出優秀的程式碼:從 HowDoI 和 Diamond 中學習

在閱讀優秀的開源程式碼時,我們可以從中學習到許多程式設計的最佳實踐和設計原則。本文將以 HowDoI 和 Diamond 為例,探討如何寫出優秀的程式碼。

讓每個函式只做一件事

HowDoI 的開發者強調,將內部函式分離出來,讓每個函式只做一件事是非常有益的。這樣做可以提高程式碼的可讀性和可維護性。例如,HowDoI 中的 _get_result() 函式可能會丟擲未捕捉的異常,因此在其上層函式 howdoi() 中進行例外處理。

def howdoi():
    try:
        result = _get_result()
        # ...
    except Exception as e:
        # 處理異常
        pass

內容解密:

  • howdoi() 函式呼叫 _get_result() 並處理可能的異常。
  • 將例外處理集中在 howdoi() 中,避免在其他函式中重複處理異常。
  • 這樣的設計使得程式碼更加清晰和易於維護。

利用系統提供的資料

HowDoI 會檢查並使用相關的系統值,例如 urllib.request.getproxies(),以處理代理伺服器的使用。這表明了了解所使用的函式庫 API 的重要性。

XDG_CACHE_DIR = os.environ.get(
    'XDG_CACHE_HOME',
    os.path.join(os.path.expanduser('~'), '.cache')
)

內容解密:

  • 程式碼使用 os.environ.get() 取得環境變數 XDG_CACHE_HOME 的值。
  • 如果該變數不存在,則使用預設值,即使用者主目錄下的 .cache 資料夾。
  • 這種設計使得程式碼能夠適應不同的系統環境。

程式碼風格

HowDoI 大致遵循 PEP 8,但並不拘泥於此。某些情況下,為了提高可讀性,會放寬某些限制。例如,USER_AGENTS 中的字串常數超過 80 個字元,但由於沒有自然的斷行位置,因此保持原樣。

使用下劃線字首的函式名稱

HowDoI 中的大多數函式都以下劃線為字首,表示它們是內部使用的。這是因為這些函式可能會丟擲未捕捉的異常,或者它們不打算在套件外部使用。

def _is_question(link):
    return re.search('questions/\d+/', link)

內容解密:

  • _is_question() 函式檢查給定的連結是否為 Stack Overflow 上的問題。
  • 使用下劃線字首表示該函式是內部使用的,不建議外部呼叫。
  • 這種命名約定提高了程式碼的可讀性。

處理相容性問題

HowDoI 將相容性問題集中在程式碼的前部處理,以避免在主體程式碼中出現版本檢查。這樣做使得使用者無需更改 Python 環境即可使用該工具。

try:
    from urllib.parse import quote as url_quote
except ImportError:
    from urllib import quote as url_quote

內容解密:

  • 程式碼嘗試從 urllib.parse 匯入 quote 函式,並將其別名為 url_quote
  • 如果匯入失敗(例如在 Python 2 中),則從 urllib 匯入 quote 函式。
  • 這種處理方式確保了程式碼在不同 Python 版本中的相容性。

Pythonic 的選擇

HowDoI 中的某些程式碼片段展現了 Pythonic 的設計選擇。例如,get_link_at_pos() 函式傳回指定位置的連結,如果沒有結果則傳回 False

def get_link_at_pos(links, position):
    links = [link for link in links if _is_question(link)]
    if not links:
        return False
    if len(links) >= position:
        link = links[position-1]
    else:
        link = links[-1]
    return link

內容解密:

  • get_link_at_pos() 函式過濾出符合條件的連結,並傳回指定位置的連結。
  • 使用清單解析和有意義的變數名稱提高了程式碼的可讀性。
  • 提早傳回 False 減少了巢狀結構,使程式碼更加扁平化。