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 # 執行單元測試

內容解密:

  1. git clone:從GitHub下載Tablib的原始碼。
  2. virtualenv:建立一個虛擬環境,以避免與系統Python環境衝突。
  3. pip install --editable .:以可編輯模式安裝Tablib,便於開發與測試。
  4. 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}

內容解密:

  1. tablib.Dataset():建立一個新的Dataset物件。
  2. data.append((fname, lname)):將資料新增至Dataset中。
  3. data.dictdata.csvdata.yaml:展示了不同格式的輸出。
  4. data.headers:設定資料的標題欄位。
  5. 將CSV資料寫入檔案並重新讀取,驗證資料的一致性。

Tablib的原始碼結構

Tablib的檔案結構如下:

tablib/
|--- __init__.py
|--- compat.py
|--- core.py
|--- formats/
|--- packages/

內容解密:

  1. __init__.py:定義了Tablib的頂層API,包括主要的類別和函式。
  2. compat.py:處理Python 2與Python 3之間的相容性問題。
  3. core.py:實作了Tablib的核心物件,如DatasetDatabook
  4. formats/packages/:分別處理不同資料格式的支援和其他依賴套件。

Tablib的檔案產生

Tablib的檔案使用了Sphinx,一個強大的檔案產生工具。可以透過以下步驟自行編譯檔案:

(venv)$ pip install sphinx
(venv)$ cd docs
(venv)$ make html
(venv)$ open _build/html/index.html # 檢視結果

內容解密:

  1. pip install sphinx:安裝Sphinx。
  2. make html:編譯檔案為HTML格式。
  3. 開啟產生的HTML檔案檢視結果。

自訂Sphinx主題

Tablib使用了自訂的Sphinx主題,存放在docs/_themes/kr/。透過修改docs/conf.py中的設定,可以選用不同的主題。

html_theme_path = ['_themes']
html_theme = 'kr'

內容解密:

  1. html_theme_path:指定自訂主題的路徑。
  2. html_theme:選擇要使用的主題名稱。

自動產生API檔案

Tablib使用Sphinx的autoclass指令自動從原始碼中的docstring產生API檔案。

.. autoclass:: Dataset
   :inherited-members:

內容解密:

  1. .. autoclass:: Dataset:自動產生Dataset類別的檔案。
  2. :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)

內容解密:

  1. export_set 函式:此函式用於將資料集匯出到 CSV 格式。它首先建立一個 StringIO 物件來模擬檔案串流,然後使用 csv.writer 將資料集的標題和資料列寫入串流中。最後,傳回串流的值,即 CSV 格式的資料。
  2. import_set 函式:此函式用於從 CSV 格式匯入資料集。它使用 csv.reader 讀取輸入串流中的 CSV 資料,然後清除現有的資料集,並將讀取到的資料逐行追加到資料集中。

描述符和屬性裝飾器(在 API 有益時設計不可變性)

Tablib 是第一個使用 Python 裝飾器語法的圖書館。裝飾器語法使用 @ 符號放在另一個函式的上方,以修改或「裝飾」下方的函式。在下面的範例中,property 裝飾器將 Dataset.heightDataset.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

內容解密:

  1. height 屬性:此屬性傳回資料集目前的列數。它透過 len(self._data) 計算列數,並且由於使用了 @property 裝飾器,因此可以像存取屬性一樣存取它,但不能直接指定修改它。
  2. 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

內容解密:

  1. 存取 heightwidth 屬性:建立一個 Dataset 物件後,可以像存取屬性一樣存取其 heightwidth,這得益於 @property 裝飾器的使用。
  2. 嘗試修改 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

內容解密:

  1. @classmethod 修飾符將 _register_formats 方法轉換為類別方法,使其能夠在類別層級操作。
  2. formats.available 包含了所有支援的檔案格式模組。
  3. 使用 setattr 動態設定 Dataset 類別的屬性,將各個格式的匯入/匯出功能繫結到對應的屬性上。
  4. 使用 property 將匯出 (export_set) 和匯入 (import_set) 功能繫結到屬性上,實作了 getter 和 setter 功能。
  5. 例外處理機制確保了當某個格式缺少匯入或匯出功能時,仍然能夠正確註冊其他功能。

屬性註冊與使用

註冊後的屬性可以直接用於資料集的匯入與匯出操作,例如:

data = tablib.Dataset()
data.tsv = 'age\tfirst_name\tlast_name\n90\tJohn\tAdams'
print(data.tsv)

內容解密:

  1. data.tsv 的 setter 被呼叫,觸發了 formats._tsv.import_set 方法,將 TSV 格式的字串匯入資料集。
  2. print(data.tsv) 則觸發了 getter,呼叫 formats._tsv.export_set 方法,將資料集匯出為 TSV 格式的字串。

設計優勢

  1. 擴充性:新增檔案格式支援變得非常簡單,只需在 formats 子套件中新增對應的模組並更新 available 列表即可。
  2. 一致性:統一的介面設計使得不同檔案格式的操作方式保持一致,簡化了使用者的學習成本。
  3. 維護性:核心邏輯與具體格式實作的分離,使得程式碼的維護變得更加容易。

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

內容解密:

  1. @property 裝飾器允許我們定義 getter 方法,使屬性可以像普通屬性一樣被存取。
  2. 使用 @height.setter 可以定義 setter 方法,控制屬性的指定過程。
  3. 這種做法可以讓我們在不破壞介面的情況下,新增額外的邏輯,如資料驗證。

Vendorized Dependencies:相依性管理的策略

Tablib 將其相依性封裝在 packages 目錄下,這是一種稱為「vendorizing」的技術。這樣做的好處是可以減少使用者需要下載的相依性數量,並且可以確保相容性。

# tablib/compat.py 中的範例
try:
    import xlrd
except ImportError:
    from packages import xlrd

內容解密:

  1. 將相依性封裝在專案中,可以避免因相依性版本不相容而導致的問題。
  2. 使用 try-except 區塊,可以根據是否安裝了相依性來決定是否使用 vendorizing 的版本。
  3. 這種做法使得 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)

內容解密:

  1. __slots__ 允許我們指定例項屬性,從而避免建立 __dict__,減少記憶體使用。
  2. Row 類別中使用 __slots__,可以節省大量的記憶體,因為可能會建立數千個 Row 物件。
  3. 需要注意的是,使用 __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

內容解密:

  1. 透過定義特殊方法,如 __getitem____setitem____delitem__,可以讓 Dataset 物件支援使用方括號 ([]) 進行操作。
  2. 這種做法使得 API 更加直觀和易於使用,提升了程式碼的可讀性和可維護性。
  3. 使用者可以像操作串列一樣,對 Dataset 物件進行索引、切片和指定等操作。