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')
內容解密:
- 日誌記錄等級:上述範例均將日誌記錄等級設為
DEBUG,這意味著所有等級的日誌訊息(包括DEBUG、INFO、WARNING、ERROR和CRITICAL)都會被記錄。 - 格式化器:格式化器定義了日誌訊息的格式。在上述範例中,格式字串
%(asctime)s %(name)-12s %(levelname)-8s %(message)s表示每條日誌訊息將包含時間戳、記錄器名稱、日誌等級和實際訊息。 - 處理器:處理器決定了日誌訊息的輸出目的地。在上述範例中,使用了
StreamHandler將日誌訊息輸出到標準錯誤流。
開源專案授權選擇
在發布開源專案時,選擇合適的授權協定至關重要。這決定了其他人如何使用、修改和分發你的程式碼。
上游授權的影響
如果你的專案衍生自另一個專案,你的授權選擇可能會受到上游專案授權的限制。例如,Python 軟體基金會(PSF)要求對 Python 原始碼的貢獻者簽署貢獻者協定,將程式碼授權給 PSF。
授權選項
有多種開源授權可供選擇。PSF 建議使用開源促進會(OSI)批准的授權。常見的開源授權包括:
- 寬鬆式授權(如 Apache 2.0、BSD 和 MIT 授權):這些授權給予使用者很大的自由度,允許他們以各種方式使用、修改和分發軟體。
- Copyleft 授權(如 GPL 家族):這些授權要求衍生作品也必須以相同的授權條款發布,確保原始碼及其修改始終開放。
選擇合適的授權
在選擇授權時,你需要考慮你的專案需求和目標。例如,如果你希望你的程式碼被廣泛採用,寬鬆式授權可能更合適。如果你希望確保衍生作品保持開放,Copyleft 授權可能是更好的選擇。
內容解密:
- 寬鬆式與 Copyleft 授權的區別:寬鬆式授權提供了更大的靈活性,而 Copyleft 授權則確保了原始碼的開放性。
- 相容性問題:不同的授權之間可能存在相容性問題,例如,Apache 2.0 授權的程式碼可以與 GPLv3 專案結合,但 GPLv2 與 Apache 2.0 不相容。
- 選擇建議:選擇 OSI 批准的授權,並考慮你的專案目標和社群期望。
綜上所述,無論是日誌記錄還是開源專案的授權選擇,都需要根據具體需求和目標進行仔細考慮。透過合理的組態和選擇,可以更好地實作專案目標並促進社群的發展。
閱讀優秀程式碼的技巧與實踐
在軟體開發的世界中,閱讀他人的程式碼是一項基本且重要的技能。Python 的設計哲學之一便是強調可讀性,而要成為一名優秀的程式設計師,閱讀、理解並學習優秀的程式碼是必經之路。本章節將透過幾個知名的 Python 專案來探討如何閱讀優秀的程式碼,並分享相關技巧。
精選專案介紹
本章節將介紹以下幾個 Python 專案:
- HowDoI:一個根據命令列的應用程式,能夠搜尋網路上的程式設計問題答案。
- Diamond:一個 Python 守護程式,用於收集系統指標並將其釋出到 Graphite 等後端。
- Tablib:一個與格式無關的表格資料集函式庫。
- Requests:一個為人類設計的 HTTP 函式庫,簡化了密碼驗證和多部分檔案上傳等操作。
- Werkzeug:最初是一系列用於 Web Service Gateway Interface(WSGI)應用的工具集,現已發展成為最先進的 WSGI 實用模組之一。
- Flask:一個根據 Werkzeug 和 Jinja2 的 Python 網頁微框架,適合快速建立簡單的網頁應用。
這些專案在程式碼可讀性方面都有出色的表現,以下是它們的一些共同特點:
共同特點分析
| 專案名稱 | 授權條款 | 程式碼行數 | 檔案字串比例 | 註解比例 | 空白行比例 | 平均函式長度 |
|---|---|---|---|---|---|---|
| HowDoI | MIT | 262 | 0% | 6% | 20% | 13 行 |
| Diamond | MIT | 6,021 | 21% | 9% | 16% | 11 行 |
| Tablib | MIT | 1,802 | 19% | 4% | 27% | 8 行 |
| Requests | Apache 2.0 | 4,072 | 23% | 8% | 19% | 10 行 |
| Flask | BSD 3-clause | 10,163 | 7% | 12% | 11% | 13 行 |
| Werkzeug | BSD 3-clause | 25,822 | 25% | 3% | 13% | 9 行 |
程式碼閱讀技巧
在閱讀這些專案的程式碼時,我們採用了不同的技巧來瞭解專案的功能和實作細節。以下是一些值得注意的觀察:
程式碼結構與註解
大多數專案都保持著良好的程式碼結構,函式長度適中,並且使用了適當的檔案字串和註解來解釋程式碼的功能。例如,Werkzeug 和 Requests 都使用了大量的檔案字串來說明函式和類別的用途。
程式碼可讀性
這些專案的程式碼都具有很高的可讀性。例如,Tablib 的程式碼結構清晰,易於理解。HowDoI 的程式碼則展現瞭如何在不使用檔案字串的情況下,透過簡潔明瞭的註解和程式碼來達到良好的可讀性。
常見的最佳實踐
- 保持函式簡短:大多數函式的長度都控制在20行以內,這使得程式碼更容易理解和維護。
- 適當使用空白行:適當的空白行可以提高程式碼的可讀性,使不同邏輯區塊之間的區隔更加明顯。
- 使用檔案字串和註解:檔案字串和註解對於解釋程式碼的功能和邏輯至關重要,尤其是在複雜的專案中。
如何提升自己的程式碼閱讀能力
- 選擇優秀的開源專案:選擇一些知名的開源專案來閱讀,例如本章節介紹的幾個專案。
- 從簡單的專案開始:先從較小的專案開始,例如 HowDoI 或 Tablib,然後逐漸轉向更複雜的專案,如 Werkzeug 或 Flask。
- 關注程式碼結構和註解:在閱讀程式碼時,注意其結構、註解和檔案字串的使用。
- 實踐和分享:嘗試將學到的技巧和最佳實踐應用到自己的專案中,並與他人分享你的學習成果。
閱讀優秀程式碼:以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
內容解密:
- 使用
git clone從GitHub下載HowDoI專案。 - 透過
virtualenv建立虛擬環境並啟用。 - 切換到專案目錄並以可編輯模式安裝。
- 執行單元測試以驗證安裝正確性。
瞭解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
內容解密:
QUERY為必填引數,表示要查詢的問題。-p引數用於指定答案的位置,預設為1。-a引數顯示完整答案內容。-c引數啟用彩色輸出。-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]
內容解密:
- 使用
--num-answers 2引數要求傳回兩個答案。 - 第一個答案展示瞭如何在列表推導式中使用lambda函式。
- 第二個答案則演示了另一種使用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的主要函式呼叫流程
內容解密:
command_line_runner()解析輸入引數並呼叫howdoi()。howdoi()包裝了_get_instructions(),處理連線錯誤。_get_instructions()呼叫_get_links()進行Google搜尋,並對每個連結呼叫_get_answer()。_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 ...
)
內容解密:
entry_points關鍵字定義了可執行的指令。console_scripts清單中指定了howdoi指令對應到howdoi.howdoi.command_line_runner()函式。
結構範例與心得
HowDoI的程式碼結構展示了以下優秀實踐:
- 單一職責原則:每個函式只負責單一任務。
- 清晰的函式命名:函式名稱直接反映其功能。
- 結構化的錯誤處理:適當使用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減少了巢狀結構,使程式碼更加扁平化。