Tablib 藉由 Dataset 和 Databook 物件,簡化了不同格式資料的處理流程。它支援多種格式,包含 CSV、JSON、YAML、Excel 等,方便資料的匯入、匯出及操作。除了基本功能外,Tablib 也提供了一些進階用法,例如動態格式註冊、描述符和屬性裝飾器等,讓開發者能更彈性地運用。其原始碼結構清晰,使用模組作為名稱空間,避免不必要的類別,提升程式碼的可讀性。此外,Tablib 使用 Sphinx 產生檔案,並支援自訂主題及自動產生 API 檔案,方便開發者查閱和使用。
Tablib:一個強大的資料格式轉換函式庫
Tablib是一個功能豐富的Python函式庫,專門用於不同資料格式之間的轉換。它透過Dataset物件或多個Dataset組成的Databook來儲存資料。Tablib支援從JSON、YAML、DBF和CSV等格式匯入資料,並能將資料匯出為XLSX、XLS、ODS、JSON、YAML、DBF、CSV、TSV和HTML等多種格式。
取得與使用Tablib
由於Tablib是一個函式庫而非應用程式,因此沒有像HowDoI或Diamond那樣單一的進入點。首先,從GitHub取得Tablib原始碼:
$ git clone https://github.com/kennethreitz/tablib.git
$ virtualenv -p python3 venv
$ source venv/bin/activate
(venv)$ cd tablib
(venv)$ pip install --editable .
(venv)$ python test_tablib.py # 執行單元測試
內容解密:
git clone:從GitHub下載Tablib的原始碼。virtualenv:建立一個虛擬環境,以避免與系統Python環境衝突。pip install --editable .:以可編輯模式安裝Tablib,便於開發與測試。python test_tablib.py:執行Tablib的單元測試,確保函式庫正確運作。
探索Tablib的檔案與API
Tablib的檔案一開始就介紹了一個使用案例,接著詳細描述了其功能。Tablib提供了Dataset物件,該物件具有列、標題和欄位。可以對Dataset物件進行各種格式的輸入/輸出操作。
使用Tablib的範例
>>> import tablib
>>> data = tablib.Dataset()
>>> names = ('Black Knight', 'Killer Rabbit')
>>> for name in names:
... fname, lname = name.split()
... data.append((fname, lname))
...
>>> data.dict
[['Black', 'Knight'], ['Killer', 'Rabbit']]
>>> print(data.csv)
Black,Knight
Killer,Rabbit
>>> data.headers = ('First name', 'Last name')
>>> print(data.yaml)
- {First name: Black, Last name: Knight}
- {First name: Killer, Last name: Rabbit}
>>> with open('tmp.csv', 'w') as outfile:
... outfile.write(data.csv)
...
>>> newdata = tablib.Dataset()
>>> newdata.csv = open('tmp.csv').read()
>>> print(newdata.yaml)
- {First name: Black, Last name: Knight}
- {First name: Killer, Last name: Rabbit}
內容解密:
tablib.Dataset():建立一個新的Dataset物件。data.append((fname, lname)):將資料新增至Dataset中。data.dict、data.csv、data.yaml:展示了不同格式的輸出。data.headers:設定資料的標題欄位。- 將CSV資料寫入檔案並重新讀取,驗證資料的一致性。
Tablib的原始碼結構
Tablib的檔案結構如下:
tablib/
|--- __init__.py
|--- compat.py
|--- core.py
|--- formats/
|--- packages/
內容解密:
__init__.py:定義了Tablib的頂層API,包括主要的類別和函式。compat.py:處理Python 2與Python 3之間的相容性問題。core.py:實作了Tablib的核心物件,如Dataset和Databook。formats/和packages/:分別處理不同資料格式的支援和其他依賴套件。
Tablib的檔案產生
Tablib的檔案使用了Sphinx,一個強大的檔案產生工具。可以透過以下步驟自行編譯檔案:
(venv)$ pip install sphinx
(venv)$ cd docs
(venv)$ make html
(venv)$ open _build/html/index.html # 檢視結果
內容解密:
pip install sphinx:安裝Sphinx。make html:編譯檔案為HTML格式。- 開啟產生的HTML檔案檢視結果。
自訂Sphinx主題
Tablib使用了自訂的Sphinx主題,存放在docs/_themes/kr/。透過修改docs/conf.py中的設定,可以選用不同的主題。
html_theme_path = ['_themes']
html_theme = 'kr'
內容解密:
html_theme_path:指定自訂主題的路徑。html_theme:選擇要使用的主題名稱。
自動產生API檔案
Tablib使用Sphinx的autoclass指令自動從原始碼中的docstring產生API檔案。
.. autoclass:: Dataset
:inherited-members:
內容解密:
.. autoclass:: Dataset:自動產生Dataset類別的檔案。:inherited-members::包含從父類別繼承的成員。
Tablib 的結構範例
Tablib 的主要特點是其 tablib/formats/ 模組中未使用類別(classes),這完美地印證了我們先前關於避免過度使用類別的觀點。接下來,我們將展示 Tablib 如何使用裝飾器語法(decorator syntax)和屬性類別(property class)來建立衍生屬性,如資料集(dataset)的高度和寬度,以及如何動態註冊檔案格式以避免重複的樣板程式碼(boilerplate code)。
沒有不必要的物件導向程式碼(使用名稱空間來分組函式)
formats 目錄包含了所有定義的檔案格式,用於輸入/輸出。這些模組名稱,如 _csv.py、_tsv.py、_json.py 等,前面加上了底線,這表示這些模組不打算直接被圖書館使用者使用。我們可以進入 formats 目錄,搜尋類別和函式。使用 grep ^class formats/*.py 指令可以發現沒有類別定義,而使用 grep ^def formats/*.py 則顯示每個模組包含一些或全部以下函式:
detect(stream):根據串流內容推斷檔案格式。dset_sheet(dataset, ws):格式化 Excel 試算表儲存格。export_set(dataset):將資料集匯出到給定的格式,傳回格式化後的字串。import_set(dset, in_stream, headers=True):將輸入串流的內容替換資料集的內容。export_book(databook):將資料簿中的資料表匯出到給定的格式,傳回字串或位元組物件。import_book(dbook, in_stream, headers=True):將輸入串流的內容替換資料簿的內容。
這是使用模組作為名稱空間(namespaces)來分隔函式的典型例子,而不是使用不必要的類別。我們可以從函式名稱中瞭解其用途,例如 formats._csv.import_set()、formats._tsv.import_set() 和 formats._json.import_set() 分別從 CSV、TSV 和 JSON 格式的檔案匯入資料集。
程式碼範例:
# tablib/formats/_csv.py 中的部分程式碼
import csv
def export_set(dataset):
"""將資料集匯出到 CSV 格式"""
stream = StringIO()
writer = csv.writer(stream)
writer.writerow(dataset.headers)
for row in dataset:
writer.writerow(row)
return stream.getvalue()
def import_set(dset, in_stream, headers=True):
"""從 CSV 格式匯入資料集"""
reader = csv.reader(in_stream.splitlines())
dset.wipe()
for row in reader:
dset.append(row)
內容解密:
export_set函式:此函式用於將資料集匯出到 CSV 格式。它首先建立一個StringIO物件來模擬檔案串流,然後使用csv.writer將資料集的標題和資料列寫入串流中。最後,傳回串流的值,即 CSV 格式的資料。import_set函式:此函式用於從 CSV 格式匯入資料集。它使用csv.reader讀取輸入串流中的 CSV 資料,然後清除現有的資料集,並將讀取到的資料逐行追加到資料集中。
描述符和屬性裝飾器(在 API 有益時設計不可變性)
Tablib 是第一個使用 Python 裝飾器語法的圖書館。裝飾器語法使用 @ 符號放在另一個函式的上方,以修改或「裝飾」下方的函式。在下面的範例中,property 裝飾器將 Dataset.height 和 Dataset.width 方法轉換為描述符(descriptors),即至少定義了 __get__()、__set__() 或 __delete__() 方法之一的類別。
程式碼範例:
class Dataset(object):
@property
def height(self):
"""目前資料集中的列數,不能直接修改"""
return len(self._data)
@property
def width(self):
"""目前資料集中的欄數,不能直接修改"""
try:
return len(self._data[0])
except IndexError:
try:
return len(self.headers)
except TypeError:
return 0
內容解密:
height屬性:此屬性傳回資料集目前的列數。它透過len(self._data)計算列數,並且由於使用了@property裝飾器,因此可以像存取屬性一樣存取它,但不能直接指定修改它。width屬性:此屬性傳回資料集目前的欄數。它嘗試根據第一列的長度計算欄數,如果資料集為空,則嘗試根據標題的長度計算,如果標題也為空,則傳回 0。同樣地,由於使用了@property裝飾器,因此可以像存取屬性一樣存取它,但不能直接指定修改它。
使用範例:
>>> import tablib
>>> data = tablib.Dataset()
>>> data.headers = ("amount", "ingredient")
>>> data.append(("2 cubes", "Arcturan Mega-gin"))
>>> data.width
2
>>> data.height
1
>>> data.height = 3
AttributeError: can't set attribute
內容解密:
- 存取
height和width屬性:建立一個Dataset物件後,可以像存取屬性一樣存取其height和width,這得益於@property裝飾器的使用。 - 嘗試修改
height屬性:由於height屬性是唯讀的,因此嘗試指定會引發AttributeError。
Tablib 檔案格式處理機制解析
Tablib 是一個強大的資料處理函式庫,其檔案格式處理機制是其核心功能之一。本文將探討 Tablib 如何實作多種檔案格式的匯入與匯出。
動態註冊檔案格式
Tablib 採用動態註冊的方式來管理支援的檔案格式,這種設計使得新增或移除格式變得非常靈活。
formats 子套件結構
Tablib 將所有檔案格式的處理例程放在 formats 子套件中,這種結構設計使主要的 core.py 模組保持簡潔,同時也讓整個套件具有良好的模組化特性。
# formats/__init__.py 中的關鍵程式碼
from . import _csv as csv
from . import _json as json
from . import _xls as xls
# ... 其他格式匯入
available = (json, xls, yaml, csv, tsv, html, xlsx, ods)
動態序號產生器制實作
Dataset 類別中的 _register_formats 方法負責動態註冊各個檔案格式:
# Dataset 類別中的 _register_formats 方法
@classmethod
def _register_formats(cls):
"""Adds format properties."""
for fmt in formats.available:
try:
setattr(cls, fmt.title, property(fmt.export_set, fmt.import_set))
except AttributeError:
try:
setattr(cls, fmt.title, property(fmt.export_set))
except AttributeError:
pass
內容解密:
@classmethod修飾符將_register_formats方法轉換為類別方法,使其能夠在類別層級操作。formats.available包含了所有支援的檔案格式模組。- 使用
setattr動態設定Dataset類別的屬性,將各個格式的匯入/匯出功能繫結到對應的屬性上。 - 使用
property將匯出 (export_set) 和匯入 (import_set) 功能繫結到屬性上,實作了 getter 和 setter 功能。 - 例外處理機制確保了當某個格式缺少匯入或匯出功能時,仍然能夠正確註冊其他功能。
屬性註冊與使用
註冊後的屬性可以直接用於資料集的匯入與匯出操作,例如:
data = tablib.Dataset()
data.tsv = 'age\tfirst_name\tlast_name\n90\tJohn\tAdams'
print(data.tsv)
內容解密:
data.tsv的 setter 被呼叫,觸發了formats._tsv.import_set方法,將 TSV 格式的字串匯入資料集。print(data.tsv)則觸發了 getter,呼叫formats._tsv.export_set方法,將資料集匯出為 TSV 格式的字串。
設計優勢
- 擴充性:新增檔案格式支援變得非常簡單,只需在
formats子套件中新增對應的模組並更新available列表即可。 - 一致性:統一的介面設計使得不同檔案格式的操作方式保持一致,簡化了使用者的學習成本。
- 維護性:核心邏輯與具體格式實作的分離,使得程式碼的維護變得更加容易。
Tablib 的檔案格式處理機制展現了優秀的軟體設計理念,值得學習與借鑒。透過動態序號產生器制,Tablib 成功地將多種檔案格式的支援整合到統一的介面中,大大提升了函式庫的可用性和可擴充性。
Python 程式設計的藝術:Tablib 專案的技術解析
Tablib 是一個強大的 Python 函式庫,用於處理資料集和資料簿。在其原始碼中,我們可以看到許多 Python 程式設計的最佳實踐和技術細節。本文將探討 Tablib 的程式碼,分析其設計決策和實作細節。
使用 @property 裝飾器:資料封裝的最佳實踐
在 Tablib 中,@property 裝飾器被用來實作資料封裝。與 Java 等語言不同,Python 的哲學是「我們都是負責任的使用者」,因此 @property 的主要目的是分離資料和與資料相關的檢視函式,而不是控制使用者對資料的存取。
@property
def height(self):
return self._height
@height.setter
def height(self, value):
# 進行必要的驗證或轉換
self._height = value
內容解密:
@property裝飾器允許我們定義 getter 方法,使屬性可以像普通屬性一樣被存取。- 使用
@height.setter可以定義 setter 方法,控制屬性的指定過程。 - 這種做法可以讓我們在不破壞介面的情況下,新增額外的邏輯,如資料驗證。
Vendorized Dependencies:相依性管理的策略
Tablib 將其相依性封裝在 packages 目錄下,這是一種稱為「vendorizing」的技術。這樣做的好處是可以減少使用者需要下載的相依性數量,並且可以確保相容性。
# tablib/compat.py 中的範例
try:
import xlrd
except ImportError:
from packages import xlrd
內容解密:
- 將相依性封裝在專案中,可以避免因相依性版本不相容而導致的問題。
- 使用
try-except區塊,可以根據是否安裝了相依性來決定是否使用 vendorizing 的版本。 - 這種做法使得 Tablib 可以支援多個 Python 版本,並且減少了使用者的安裝負擔。
使用 __slots__ 最佳化記憶體使用
Tablib 使用 __slots__ 來最佳化 Row 物件的記憶體使用。__slots__ 可以減少每個例項的記憶體佔用,特別是在有大量小物件的情況下。
class Row(object):
__slots__ = ['_row', 'tags']
def __init__(self, row=list(), tags=list()):
self._row = list(row)
self.tags = list(tags)
內容解密:
__slots__允許我們指定例項屬性,從而避免建立__dict__,減少記憶體使用。- 在
Row類別中使用__slots__,可以節省大量的記憶體,因為可能會建立數千個Row物件。 - 需要注意的是,使用
__slots__後,物件將沒有__dict__屬性,因此需要定義__getstate__和__setstate__方法來支援 pickling。
自定義運算元過載:提升程式碼的可讀性
Tablib 實作了運算元過載,使得對 Dataset 物件的操作更加直觀和方便。
class Dataset(object):
def __getitem__(self, key):
# 實作根據索引或欄位名稱存取資料的邏輯
pass
def __setitem__(self, key, value):
# 實作根據索引或欄位名稱設定資料的邏輯
pass
def __delitem__(self, key):
# 實作根據索引或欄位名稱刪除資料的邏輯
pass
內容解密:
- 透過定義特殊方法,如
__getitem__、__setitem__和__delitem__,可以讓Dataset物件支援使用方括號 ([]) 進行操作。 - 這種做法使得 API 更加直觀和易於使用,提升了程式碼的可讀性和可維護性。
- 使用者可以像操作串列一樣,對
Dataset物件進行索引、切片和指定等操作。