返回文章列表

Python 模組檔案化最佳實務與套件組織技巧

本文探討 Python 模組的檔案化最佳實務,涵蓋 Docstrings 的撰寫規範、模組的組織方式以及如何利用套件有效管理專案程式碼,並分享如何釋出套件到 PyPI,讓你的程式碼更容易被社群分享和使用。

軟體開發 Python

Python 的動態特性使得程式碼檔案至關重要。良好的檔案不僅能提升程式碼可讀性,更能促進團隊協作和程式碼維護。本文將探討 Python 模組的檔案化最佳實務,並分享玄貓多年來的實戰經驗。

在 Python 中,我們使用 Docstrings 來為模組、類別和函式增加檔案。Docstrings 是一種特殊的字串,位於程式碼區塊的開頭,用於描述該區塊的功能和使用方法。與其他語言不同的是,Python 的 Docstrings 可以在程式執行時被存取,這使得互動式開發和檔案生成更加便捷。

撰寫 Docstrings 時,我們需要遵循一些規範。首先,Docstrings 應該使用三個雙引號 (""") 包裹。對於模組的 Docstrings,第一行應該簡潔地描述模組的功能,後續段落則可以提供更詳細的說明,例如使用方法、注意事項等等。如果是指令列工具,也應該在 Docstrings 中包含使用說明。

類別和函式的 Docstrings 也應該遵循類別似的規範。類別的 Docstrings 應該描述類別的功能和屬性,而函式的 Docstrings 則應該描述函式的引數、傳回值和作用。此外,我們還可以使用 Args:Returns:Raises: 等標籤來更清晰地描述函式的不同部分。

除了 Docstrings,我們還可以利用套件來組織和管理程式碼。套件本質上是一個包含 __init__.py 檔案的目錄,用於將相關的模組組織在一起。使用套件可以避免命名衝突,並提升程式碼的可維護性。

當你的專案變得龐大時,將程式碼封裝成套件並釋出到 PyPI (Python Package Index) 是一個好主意。PyPI 是一個公開的 Python 套件倉函式庫,可以讓你的程式碼更容易被社群分享和使用。要釋出套件到 PyPI,你需要準備一個 setup.py 檔案,其中包含套件的資訊,例如名稱、版本、作者等等。

總之,良好的檔案和程式碼組織是 Python 開發的關鍵。透過遵循 Docstrings 的撰寫規範、使用套件組織程式碼,以及將程式碼釋出到 PyPI,你可以讓你的程式碼更容易被理解、使用和維護。

序列化疑難雜症:用 copyreg 開發穩定的物件儲存方案

在軟體開發中,序列化 (Serialization) 扮演著重要的角色。它能將程式中的物件轉換為位元組流,方便儲存到檔案、資料函式庫,或透過網路傳輸。Python 的 pickle 模組是個常見的序列化工具,但它並非萬能。有時候,你會遇到一些棘手的問題,像是版本不相容、類別重新命名等。這時候,copyreg 模組就能派上用場,幫助你開發更穩定的物件儲存方案。

pickle 的限制:脆弱的序列化

pickle 雖然方便,但它在某些情況下會變得相當脆弱。舉例來說,當你的類別結構發生變化時,舊的序列化物件可能就無法正確還原。

import pickle

class GameState:
    def __init__(self, level=0, points=0, magic=5):
        self.level = level
        self.points = points
        self.magic = magic

state = GameState()
serialized = pickle.dumps(state)
print(serialized)

如果我們在 GameState 類別中新增一個屬性,例如 lives,舊的序列化資料就會因為缺少這個屬性而無法載入。

copyreg 救援:版本控制與相容性

copyreg 模組提供了一種機制,讓你能夠自訂物件的序列化與反序列化過程。這對於處理版本相容性問題非常有用。

版本遷移:新增屬性

假設我們在 GameState 類別中新增了 lives 屬性,為了確保舊的序列化資料能夠正確載入,我們可以定義一個反序列化函式,在載入舊物件時,為 lives 屬性設定一個預設值。

import copyreg
import pickle

class GameState:
    def __init__(self, level=0, points=0, magic=5, lives=3):
        self.level = level
        self.points = points
        self.magic = magic
        self.lives = lives

def pickle_game_state(game_state):
    kwargs = game_state.__dict__
    return unpickle_game_state, (kwargs,)

def unpickle_game_state(kwargs):
    version = kwargs.pop('version', 1)
    if version == 1:
        kwargs.pop('lives', None)  # 移除 'lives',避免舊版本錯誤
    return GameState(**kwargs)

copyreg.pickle(GameState, pickle_game_state)

state = GameState()
serialized = pickle.dumps(state)
state_after = pickle.loads(serialized)
print(state_after.__dict__)

內容解密

  1. GameState 類別: 定義了一個遊戲狀態的類別,包含了等級(level)、分數(points)、魔法值(magic)和生命值(lives)等屬性。
  2. pickle_game_state 函式: 這個函式用於在序列化 GameState 物件時進行處理。它將 GameState 物件的 __dict__ (包含物件所有屬性的字典)取出,然後傳回 unpickle_game_state 函式和這個屬性字典。
  3. unpickle_game_state 函式: 這個函式用於在反序列化 GameState 物件時進行處理。它接受一個包含屬性的字典 kwargs,並從中取出 version 鍵的值,預設為 1。如果版本是 1,它會嘗試從 kwargs 中移除 lives 鍵,這樣做是為了相容舊版本的 GameState 物件,因為舊版本可能沒有 lives 屬性。最後,它使用 kwargs 中的屬性來建立並傳回一個新的 GameState 物件。
  4. copyreg.pickle 函式: 這個函式用於註冊一個自訂的序列化和反序列化函式。在這裡,它將 GameState 類別與 pickle_game_state 函式關聯起來,這樣當 pickle 模組需要序列化 GameState 物件時,就會使用 pickle_game_state 函式。
  5. 序列化和反序列化過程: 首先建立一個 GameState 物件 state,然後使用 pickle.dumps 函式將其序列化為一個字串 serialized。接著,使用 pickle.loads 函式將 serialized 字串反序列化回一個 GameState 物件 state_after。
  6. 驗證結果: 最後印出 state_after 物件的 __dict__ 屬性,以驗證反序列化後的物件是否包含所有預期的屬性和值。

在這個範例中,pickle_game_state 函式負責處理序列化,而 unpickle_game_state 函式則負責處理反序列化。在 unpickle_game_state 函式中,我們檢查了物件的版本,如果版本是 1,就移除 lives 屬性,避免舊版本出現錯誤。

穩定載入路徑:類別重新命名

另一個常見的問題是類別重新命名。當你將 GameState 類別重新命名為 BetterGameState 時,舊的序列化資料就會因為找不到 GameState 類別而無法載入。

import copyreg
import pickle

class BetterGameState:
    def __init__(self, level=0, points=0, magic=5):
        self.level = level
        self.points = points
        self.magic = magic

def pickle_game_state(game_state):
    kwargs = game_state.__dict__
    return unpickle_game_state, (kwargs,)

def unpickle_game_state(kwargs):
    return BetterGameState(**kwargs)

copyreg.pickle(BetterGameState, pickle_game_state)

state = BetterGameState()
serialized = pickle.dumps(state)
print(serialized[:35])

內容解密

  1. BetterGameState 類別: 定義了一個遊戲狀態的類別,包含了等級(level)、分數(points)和魔法值(magic)等屬性。
  2. pickle_game_state 函式: 這個函式用於在序列化 BetterGameState 物件時進行處理。它將 BetterGameState 物件的 __dict__ (包含物件所有屬性的字典)取出,然後傳回 unpickle_game_state 函式和這個屬性字典。
  3. unpickle_game_state 函式: 這個函式用於在反序列化 BetterGameState 物件時進行處理。它接受一個包含屬性的字典 kwargs,並使用這些屬性來建立並傳回一個新的 BetterGameState 物件。
  4. copyreg.pickle 函式: 這個函式用於註冊一個自訂的序列化和反序列化函式。在這裡,它將 BetterGameState 類別與 pickle_game_state 函式關聯起來,這樣當 pickle 模組需要序列化 BetterGameState 物件時,就會使用 pickle_game_state 函式。
  5. 序列化過程: 首先建立一個 BetterGameState 物件 state,然後使用 pickle.dumps 函式將其序列化為一個字串 serialized。
  6. 印出序列化字串的前 35 個字元: 最後印出 serialized 字串的前 35 個字元,這部分內容包含了序列化物件的型別資訊和函式路徑。

為了避免這個問題,我們可以再次使用 copyreg,將 BetterGameState 類別與 unpickle_game_state 函式關聯起來。這樣,pickle 在序列化時就會儲存 unpickle_game_state 函式的路徑,而不是 BetterGameState 類別的路徑。當反序列化時,只要 unpickle_game_state 函式存在,就能夠正確載入物件,即使類別名稱已經改變。

玄貓建議: 這種方法的限制是,你不能改變 unpickle_game_state 函式所在的模組路徑。一旦使用這個函式序列化了資料,它就必須一直存在於相同的路徑下,才能確保反序列化能夠成功。

時區轉換別再踩雷:datetime 才是你的好夥伴

時間處理是程式開發中常見的需求。你可能需要將時間儲存到資料函式庫、顯示在網頁上,或進行時間相關的計算。Python 提供了 timedatetime 兩個模組來處理時間,但玄貓強烈建議你使用 datetime 模組,避免使用 time 模組,因為 time 模組在處理時區轉換時容易出錯。

time 模組:過時與易出錯

time 模組提供了一些函式來處理時間,例如 localtime 可以將 UTC 時間轉換為本地時間,mktime 可以將本地時間轉換為 UTC 時間。

from time import localtime, strftime

now = 1407694710
local_tuple = localtime(now)
time_format = '%Y-%m-%d %H:%M:%S'
time_str = strftime(time_format, local_tuple)
print(time_str)

內容解密

  1. 引入模組
    • from time import localtime, strftime:從 time 模組中引入 localtime 和 strftime 函式。
      • localtime 函式用於將時間戳轉換為本地時間的 struct_time 物件。
      • strftime 函式用於將 struct_time 物件格式化為字串。
  2. 設定時間戳
    • now = 1407694710:設定一個時間戳(timestamp),表示自 1970 年 1 月 1 日 00:00:00 UTC 以來的秒數。
  3. 轉換為本地時間
    • local_tuple = localtime(now):使用 localtime 函式將時間戳 now 轉換為本地時間的 struct_time 物件。struct_time 是一個包含年、月、日、時、分、秒等時間元素的元組。
  4. 定義時間格式
    • time_format = '%Y-%m-%d %H:%M:%S':定義時間格式字串,用於指定時間的顯示格式。
      • %Y:四位數年份(例如:2023)
      • %m:兩位數月份(01-12)
      • %d:兩位數日期(01-31)
      • %H:24 小時制小時(00-23)
      • %M:分鐘(00-59)
      • %S:秒數(00-59)
  5. 格式化時間為字串
    • time_str = strftime(time_format, local_tuple):使用 strftime 函式將本地時間的 struct_time 物件 local_tuple 格式化為指定格式的字串。
  6. 印出格式化後的字串
    • print(time_str):印出格式化後的時間字串。

但當你需要進行時區轉換時,time 模組就會變得非常麻煩。你需要手動處理時區的差異、夏令時間等問題,而與不同作業系統的時區設定可能不同,這使得程式碼的可移植性降低。

更糟糕的是,strptime 函式對於時區名稱的支援非常有限。它可能只支援某些特定的時區名稱,例如 “PDT”,但對於其他時區名稱,例如 “EDT”,則會丟擲例外。

from time import strptime

parse_format = '%Y-%m-%d %H:%M:%S %Z'
depart_sfo = '2014-05-01 15:45:16 PDT'
time_tuple = strptime(depart_sfo, parse_format)

datetime 模組:現代與可靠

datetime 模組是 Python 處理時間的現代解決方案。它提供了 datetimetimezonetimedelta 等類別,可以更方便地進行時間計算和時區轉換。

from datetime import datetime, timezone, timedelta

now_utc = datetime.now(timezone.utc)
print(now_utc)

now_local = now_utc.astimezone()
print(now_local)

內容解密

  1. 引入模組和類別
    • from datetime import datetime, timezone, timedelta:從 datetime 模組中引入 datetime、timezone 和 timedelta 類別。
      • datetime 類別用於表示日期和時間。
      • timezone 類別用於表示時區。
      • timedelta 類別用於表示時間間隔。
  2. 取得 UTC 時間
    • now_utc = datetime.now(timezone.utc):使用 datetime.now(timezone.utc) 取得目前 UTC 時間。
      • datetime.now() 函式用於取得目前日期和時間。
      • timezone.utc 表示 UTC 時區。
  3. 印出 UTC 時間
    • print(now_utc):印出目前 UTC 時間。
  4. 轉換為本地時間
    • now_local = now_utc.astimezone():使用 astimezone() 方法將 UTC 時間轉換為本地時間。
      • astimezone() 方法會根據系統設定的時區自動轉換為本地時間。
  5. 印出本地時間
    • print(now_local):印出目前本地時間。

要進行時區轉換,你需要安裝 pytz 這個第三方套件。pytz 提供了完整的時區資料函式庫,可以讓你輕鬆地將時間轉換為任何時區。

from datetime import datetime
import pytz

utc = pytz.utc
eastern = pytz.timezone('US/Eastern')
fmt = '%Y-%m-%d %H:%M:%S %Z%z'

nyc = eastern.localize(datetime(2014, 4, 9, 11, 0, 0))
print(nyc.strftime(fmt))

pacific = pytz.timezone('US/Pacific')
sf = pacific.normalize(nyc.astimezone(pacific))
print(sf.strftime(fmt))

內容解密

  1. 引入模組和類別
    • from datetime import datetime:從 datetime 模組中引入 datetime 類別。
    • import pytz:引入 pytz 模組,用於處理時區。
  2. 定義時區
    • utc = pytz.utc:定義 UTC 時區。
    • eastern = pytz.timezone('US/Eastern'):定義美國東部時區。
  3. 設定時間格式
    • fmt = '%Y-%m-%d %H:%M:%S %Z%z':定義時間格式字串,用於指定時間的顯示格式。
      • %Y:四位數年份(例如:2023)
      • %m:兩位數月份(01-12)
      • %d:兩位數日期(01-31)
      • %H:24 小時制小時(00-23)
      • %M:分鐘(00-59)
      • %S:秒數(00-59)
      • %Z:時區名稱
      • %z:UTC 偏移量
  4. 本地化時間
    • nyc = eastern.localize(datetime(2014, 4, 9, 11, 0, 0)):使用 eastern.localize() 方法將 datetime 物件本地化為美國東部時區的時間。
  5. 印出美國東部時間
    • print(nyc.strftime(fmt)):印出格式化後的美國東部時間。
  6. 轉換為太平洋時間
    • pacific = pytz.timezone('US/Pacific'):定義美國太平洋時區。
    • sf = pacific.normalize(nyc.astimezone(pacific))
      • 使用 nyc.astimezone(pacific) 將美國東部時間轉換為太平洋時間。
      • 使用 pacific.normalize() 處理夏令時間(Daylight Saving Time)的轉換。
  7. 印出太平洋時間
    • print(sf.strftime(fmt)):印出格式化後的太平洋時間。

在這個範例中,我們首先使用 pytz.timezone 建立時區物件,然後使用 localize 方法將 datetime 物件本地化為指定的時區。接著,我們可以使用 astimezone 方法將時間轉換為其他時區。最後,使用 normalize 方法處理夏令時間的轉換,確保時間的準確性。

玄貓建議: 在處理時間時,始終使用 UTC 時間作為標準時間。當需要顯示本地時間時,再將 UTC 時間轉換為本地時間。

為何我放棄 time 模組:datetime 與 pytz 的時區轉換

在 Python 中處理時間時,time 模組常因其平台依賴性而導致不可靠的行為。time 模組的實際運作方式取決於底層 C 函式如何與主機作業系統互動,這使得它在處理多個本地時間時容易出錯。玄貓建議,除非是 UTC 與主機電腦本地時間之間的轉換,否則應避免使用 time 模組。對於其他型別的轉換,datetime 模組是更可靠的選擇。

datetime 模組:更可靠的時區轉換方案

datetime 模組是 Python 內建的模組,它提供了 datetime 類別,可以更可靠地將 UTC 時間轉換為本地時間。

以下範例示範如何將 UTC 時間轉換為本地時間(太平洋夏令時間):

from datetime import datetime, timezone

now = datetime(2014, 8, 10, 18, 18, 30)
now_utc = now.replace(tzinfo=timezone.utc)
now_local = now_utc.astimezone()
print(now_local)
# 輸出:2014-08-10 11:18:30-07:00

datetime 模組也可以輕鬆地將本地時間轉換回 UTC 的 UNIX 時間戳記。

from datetime import datetime
from time import mktime

time_str = '2014-08-10 11:18:30'
time_format = '%Y-%m-%d %H:%M:%S'  # 確保 time_format 已定義
now = datetime.strptime(time_str, time_format)
time_tuple = now.timetuple()
utc_now = mktime(time_tuple)
print(utc_now)
# 輸出:1407694710.0

time 模組不同,datetime 模組提供了可靠地在不同本地時間之間進行轉換的功能。然而,datetime 僅透過其 tzinfo 類別和相關方法提供時區操作的機制,缺少 UTC 以外的時區定義。

pytz 模組:填補 datetime 的時區空白

Python 社群透過 pytz 模組填補了這個空白。pytz 包含一個完整的資料函式庫,其中包含您可能需要的所有時區定義。

要有效地使用 pytz,應始終先將本地時間轉換為 UTC。在 UTC 值上執行所需的任何 datetime 操作(例如偏移)。然後,作為最後一步,轉換為本地時間。

例如,以下是如何將紐約航班的抵達時間轉換為 UTC datetime

import pytz
from datetime import datetime

time_format = '%Y-%m-%d %H:%M:%S'
arrival_nyc = '2014-05-01 23:33:24'
nyc_dt_naive = datetime.strptime(arrival_nyc, time_format)
eastern = pytz.timezone('US/Eastern')
nyc_dt = eastern.localize(nyc_dt_naive)
utc_dt = pytz.utc.normalize(nyc_dt.astimezone(pytz.utc))
print(utc_dt)
# 輸出:2014-05-02 03:33:24+00:00

獲得 UTC datetime 後,可以將其轉換為舊金山本地時間:

pacific = pytz.timezone('US/Pacific')
sf_dt = pacific.normalize(utc_dt.astimezone(pacific))
print(sf_dt)
# 輸出:2014-05-01 20:33:24-07:00

同樣,也可以將其轉換為尼泊爾的本地時間:

nepal = pytz.timezone('Asia/Katmandu')
nepal_dt = nepal.normalize(utc_dt.astimezone(nepal))
print(nepal_dt)
# 輸出:2014-05-02 09:18:24+05:45

透過 datetimepytz,這些轉換在所有環境中都是一致的,無論主機電腦執行的是什麼作業系統。

玄貓建議:時區轉換要點

  • 避免使用 time 模組在不同時區之間進行轉換。
  • 使用內建的 datetime 模組以及 pytz 模組,以可靠地在不同時區的時間之間進行轉換。
  • 始終以 UTC 格式表示時間,並在顯示之前,作為最後一步轉換為本地時間。

內建演算法與資料結構:玄貓的效能最佳化策略

在實作處理大量資料的 Python 程式時,最終會因為程式碼的演算法複雜性而導致速度變慢。這通常不是因為 Python 作為一種語言的速度問題。更可能的問題是,您沒有為您的問題使用最佳的演算法和資料結構。

幸運的是,Python 標準函式庫內建了許多您需要使用的演算法和資料結構。除了速度之外,使用這些常見的演算法和資料結構可以讓您的生活更輕鬆。您可能想要使用的一些最有價值的工具很難正確實作。避免重新實作常見的功能將節省您的時間和精力。

雙端佇列:deque 的妙用

collections 模組中的 deque 類別是一個雙端佇列。它為從其開頭或結尾插入或刪除專案提供恆定時間操作。這使其成為先進先出 (FIFO) 佇列的理想選擇。

from collections import deque

fifo = deque()
fifo.append(1)  # 生產者
x = fifo.popleft()  # 消費者

list 內建型別也包含一個像佇列一樣的專案排序序列。您可以在恆定時間內從清單末尾插入或刪除專案。但是從清單頭部插入或刪除專案需要線性時間,這比 deque 的恆定時間慢得多。

有序字典:OrderedDict 的優勢

標準字典是無序的。這表示具有相同鍵和值的 dict 可能會導致不同的迭代順序。這種行為是字典快速雜湊表實作方式的一個令人驚訝的副產品。

from random import randint

a = {}
a['foo'] = 1
a['bar'] = 2

# 隨機填充 'b' 以導致雜湊衝突
while True:
    z = randint(99, 1013)
    b = {}
    for i in range(z):
        b[i] = i
    b['foo'] = 1
    b['bar'] = 2
    for i in range(z):
        del b[i]
    if str(b) != str(a):
        break

print(a)
print(b)
print('Equal?', a == b)
# 可能輸出:
# {'foo': 1, 'bar': 2}
# {'bar': 2, 'foo': 1}
# Equal? True

collections 模組中的 OrderedDict 類別是一種特殊的字典型別,它會追蹤其鍵插入的順序。迭代 OrderedDict 的鍵具有可預測的行為。透過使所有程式碼具有確定性,這可以大大簡化測試和偵錯。

from collections import OrderedDict

a = OrderedDict()
a['foo'] = 1
a['bar'] = 2

b = OrderedDict()
b['foo'] = 'red'
b['bar'] = 'blue'

for value1, value2 in zip(a.values(), b.values()):
    print(value1, value2)
# 輸出:
# 1 red
# 2 blue

預設字典:defaultdict 的簡化

字典對於簿記和追蹤統計資料很有用。字典的一個問題是您不能假設任何鍵已經存在。這使得做一些簡單的事情(例如遞增儲存在字典中的計數器)變得笨拙。

stats = {}
key = 'my_counter'
if key not in stats:
    stats[key] = 0
stats[key] += 1

collections 模組中的 defaultdict 類別透過在鍵不存在時自動儲存預設值來簡化此操作。您所要做的就是提供一個函式,該函式每次缺少鍵時都會傳回預設值。在本例中,int 內建函式傳回 0。現在,遞增計數器很簡單。

from collections import defaultdict

stats = defaultdict(int)
stats['my_counter'] += 1

玄貓建議,善用這些內建的演算法和資料結構,可以有效提升 Python 程式的效能和可維護性。在面對效能瓶頸時,不妨先檢視是否能透過更合適的資料結構來解決問題。

玄貓解密:Python 演算法與資料結構高效應用技巧

Python 作為資料科學與軟體開發的熱門語言,內建了許多強大的模組,能幫助我們更有效率地處理資料和實作演算法。玄貓將分享如何善用這些模組,提升你的 Python 程式碼效能與可讀性。

Heap Queue:優先佇列的實作程式碼解密

在許多應用情境中,我們需要維護一個優先佇列,確保每次都能優先處理具有最高優先順序的元素。heapq 模組提供了一組函式,讓我們能在標準的 Python 列表上輕鬆實作 Heap 結構。

from heapq import heappush, heappop, nsmallest

a = []
heappush(a, 5)
heappush(a, 3)
heappush(a, 7)
heappush(a, 4)

print(heappop(a), heappop(a), heappop(a), heappop(a))
# 輸出: 3 4 5 7

程式碼解密

  1. 引入函式:從 heapq 模組引入 heappushheappopnsmallest 函式。
  2. 建立 Heap:使用 heappush 將元素依序加入列表 a 中,heapq 會自動調整列表結構,使其符合 Heap 的特性。
  3. 取出元素:使用 heappop 從 Heap 中取出元素,每次都會取出具有最高優先順序(數值最小)的元素。
  4. nsmallest 函式:找出 heap 中最小的 n 個數值。

玄貓提示heapq 模組的函式都是以對數時間複雜度執行,相較於直接對列表進行排序,效率更高。

Bisection:二分搜尋法的閃電應用

在已排序的列表中尋找特定元素時,傳統的 index 方法需要線性時間。但 bisect 模組提供的 bisect_left 函式,能以對數時間複雜度執行二分搜尋,大幅提升效率。

from bisect import bisect_left

x = list(range(10**6))
i = bisect_left(x, 991234)
print(i)

程式碼解密

  1. 引入函式:從 bisect 模組引入 bisect_left 函式。
  2. 二分搜尋:使用 bisect_left 在已排序的列表 x 中尋找數值 991234,並傳回其插入點索引。

玄貓提示:當資料量大與需要頻繁搜尋時,bisect 模組能顯著提升效能。

Iterator Tools:迭代器的瑞士刀

itertools 模組提供了一系列強大的函式,能幫助我們更靈活地處理迭代器。這些函式大致可分為三類別:

  • 連線迭代器chaincycleteezip_longest
  • 篩選迭代器islicetakewhiledropwhilefilterfalse
  • 組合迭代器productpermutationscombinations
from itertools import chain, islice

list1 = [1, 2, 3]
list2 = [4, 5, 6]

# 使用 chain 連線兩個迭代器
chained = chain(list1, list2)
print(list(chained))  # 輸出: [1, 2, 3, 4, 5, 6]

# 使用 islice 篩選迭代器
sliced = islice(list1, 1, 3)
print(list(sliced))  # 輸出: [2, 3]

程式碼解密

  1. 引入函式:從 itertools 模組引入 chainislice 函式。
  2. 連線迭代器:使用 chainlist1list2 連線成一個新的迭代器。
  3. 篩選迭代器:使用 islicelist1 中取出索引 1 到 2 的元素。

玄貓提示itertools 模組提供了豐富的工具,能簡化複雜的迭代邏輯,提高程式碼的可讀性和效率。

Decimal:精確至上的數值計算

在處理金融或科學計算等對精確度要求極高的情境時,Python 預設的浮點數型別可能會產生誤差。decimal 模組提供的 Decimal 類別,能提供高精確度的定點數運算。

from decimal import Decimal, ROUND_UP

rate = Decimal('1.45')
seconds = Decimal('222')  # 3*60 + 42
cost = rate * seconds / Decimal('60')
print(cost)  # 輸出: 5.365

rounded = cost.quantize(Decimal('0.01'), rounding=ROUND_UP)
print(rounded)  # 輸出: 5.37

程式碼解密

  1. 引入類別:從 decimal 模組引入 Decimal 類別和 ROUND_UP 常數。
  2. 建立 Decimal 物件:使用字串初始化 Decimal 物件,避免浮點數轉換誤差。
  3. 定點數運算:使用 Decimal 物件進行運算,確保精確度。
  4. 四捨五入:使用 quantize 方法進行四捨五入,並指定 ROUND_UP 模式,確保結果符合預期。

玄貓提示:在需要極高精確度的場合,Decimal 類別是不可或缺的工具。

Python 提供了許多內建模組,能幫助我們更有效率地處理資料和實作演算法。善用這些模組,能提升你的 Python 程式碼效能與可讀性。玄貓建議大家多加探索這些模組,並將它們應用到你的專案中。

為何 Python 開發者都該知道 Decimal 類別?玄貓的精準計算經驗談

在金融科技業打滾多年,玄貓(BlackCat)深知精準計算的重要性。你是否也曾因為浮點數運算的誤差而苦惱?Python 的 Decimal 類別就是解決方案。它特別適合需要高精確度和精確四捨五入的場合,例如處理貨幣價值。

PyPI 的奧妙:玄貓帶你探索 Python 社群寶藏

身為 Python 開發者,絕對不能不知道 PyPI(Python Package Index)。PyPI 就像一個巨大的藏寶函式庫,裡面有各式各樣由社群開發和維護的模組。當你遇到陌生的挑戰時,PyPI 絕對是尋找解決方案的好地方。

要使用 PyPI,你需要用到 pip 這個指令列工具。在 Python 3.4 及以上版本,pip 已經預設安裝。如果是更早的版本,可以參考 Python Packaging 網站上的安裝說明。

安裝完成後,使用 pip 安裝新的模組非常簡單。例如,安裝 pytz 模組:

$ pip3 install pytz
Downloading/unpacking pytz
Downloading pytz-2014.4.tar.bz2 (159kB): 159kB downloaded
Running setup.py (...) egg_info for package pytz
Installing collected packages: pytz
Running setup.py install for pytz
Successfully installed pytz
Cleaning up...

pip3 指令用於安裝 Python 3 版本的套件,而 pip 指令(不含 3)則用於安裝 Python 2 的套件。現在大多數流行的套件都同時支援這兩個版本。pip 也可以搭配 pyvenv 使用,追蹤專案需要安裝的套件。

PyPI 上的每個模組都有自己的軟體授權。大多數套件,尤其是流行的套件,都採用免費或開放原始碼授權。在大多數情況下,這些授權允許你在程式中包含模組的副本。

重點整理:

  • Python Package Index (PyPI) 包含大量由 Python 社群建立和維護的常用套件。
  • pip 是用於從 PyPI 安裝套件的指令列工具。
  • pip 在 Python 3.4 及以上版本預設安裝;舊版本則需要自行安裝。
  • PyPI 上的大多數模組都是免費和開放原始碼軟體。

協同合作:玄貓的 Python 開發最佳實踐

Python 提供了許多語言特性,協助你建構良好定義的 API,並具有清晰的介面邊界。Python 社群也建立了最佳實踐,最大程度地提高程式碼的可維護性。此外,Python 還提供了一些標準工具,使大型團隊能夠在不同的環境中協同工作。

與他人協作開發 Python 程式,需要仔細考慮程式碼的撰寫方式。即使是自己開發,你也可能會使用標準函式庫或開放原始碼套件中由他人撰寫的程式碼。因此,理解使你能夠輕鬆與其他 Python 程式設計師協作的機制非常重要。

撰寫 Docstrings:玄貓的程式碼檔案化哲學

在 Python 中,檔案非常重要,因為 Python 具有動態特性。Python 內建支援將檔案附加到程式碼區塊。與許多其他語言不同,程式碼的檔案可以直接在程式執行時存取。

例如,你可以在函式的 def 陳述式之後立即提供一個 docstring 來新增檔案。

def palindrome(word):
    """
    判斷給定的單字是否為迴文。

    Args:
        word (str): 要檢查的單字。

    Returns:
        bool: 如果單字是迴文則傳回 True,否則傳回 False。

    Example:
        >>> palindrome("madam")
        True
        >>> palindrome("test")
        False
    """
    return word == word[::-1]

你可以透過存取函式的 __doc__ 特殊屬性,從 Python 程式本身檢索 docstring。

print(repr(palindrome.__doc__))

輸出:

'\n    判斷給定的單字是否為迴文。\n\n    Args:\n        word (str): 要檢查的單字。\n\n    Returns:\n        bool: 如果單字是迴文則傳回 True,否則傳回 False。\n\n    Example:\n        >>> palindrome("madam")\n        True\n        >>> palindrome("test")\n        False\n    '

Docstring 可以附加到函式、類別和模組。這種關聯是編譯和執行 Python 程式的過程的一部分。對 docstring 和 __doc__ 屬性的支援有三個後果:

  1. 檔案易於存取,使互動式開發更輕鬆。 你可以使用 help 內建函式檢查函式、類別和模組,以檢視其檔案。這使得 Python 互動式直譯器(Python “shell”)和 IPython Notebook 之類別的工具在使用時非常愉快,你可以開發演算法、測試 API 和編寫程式碼片段。
  2. 定義檔案的標準方式,可以輕鬆建構將文字轉換為更具吸引力的格式(如 HTML)的工具。 這使得 Python 社群擁有了出色的檔案產生工具,例如 Sphinx。它還啟用了 Read the Docs 之類別的社群資助網站,這些網站為開放原始碼 Python 專案提供免費託管美觀的檔案。
  3. Python 的一流、可存取與美觀的檔案鼓勵人們編寫更多檔案。 Python 社群的成員堅信檔案的重要性。他們認為「好的程式碼」也意味著有良好檔案記錄的程式碼。這表示你可以期望大多數開放原始碼 Python 函式庫都具有像樣的檔案。

為了參與這種優秀的檔案文化,你在編寫 docstring 時需要遵循一些準則。完整的詳細資訊在 PEP 257 中討論。以下是一些你應該務必遵循的最佳實務。

模組檔案化

每個模組都應該有一個頂層 docstring。這是一個字串文字,它是原始程式碼檔案中的第一個陳述式。它應該使用三個雙引號 (""")。此 docstring 的目標是介紹模組及其內容。

docstring 的第一行應該是一個描述模組用途的單一句子。後面的段落應包含模組的所有使用者都應該瞭解的有關其操作的詳細資訊。模組 docstring 也是一個跳板,你可以在其中重點介紹模組中的重要類別和函式。

以下是一個模組 docstring 的範例:

# words.py
#!/usr/bin/env python3
"""
用於測試單字各種語言模式的函式庫。

有時測試單字之間的關係可能很棘手!
此模組提供了簡單的方法來確定你找到的單字何時具有特殊屬性。

可用函式:
- palindrome:確定單字是否為迴文。
- check_anagram:確定兩個單字是否為易位構詞。
...
"""
# ...

如果模組是一個指令列工具,則模組 docstring 也是放置從指令列執行工具的使用資訊的好地方。

總之,玄貓(BlackCat)認為,良好的檔案是協同開發的根本。撰寫清晰、完整的 docstring,不僅能幫助他人理解你的程式碼,也能提升你自己的程式碼品質。