現今後端(backend)儲存解決方案相較於以往多了許多選擇,從傳統的硬碟儲存到現代各式各樣的雲端儲存服務,例如 AWS S3, Google Cloud Storage, Azure Blob Storage 甚至 HDFS(Hadoop Distributed File System) 等等,因此開發過程不免都會遇到需要整合雲端儲存方案的問題,與其重新發明輪胎,不如選擇 1 套簡單易用而且通吃各家服務的套件,不僅省事還能有效增加開發效率。

所以,你需要 smart_open 的幫忙!

本文環境

$ pip install smart_open==5.2.1

smart_open 簡介

smart_open 可以視為 Python 內建 open() 函式的替代品,不僅 100% 相容,還更加強大,只要 from smart_open import open 取代 Python 內建的 open() 就能夠與 AWS S3, Google Cloud Storage, Azure Blob Storage 甚至 HDFS(Hadoop Distributed File System) 等儲存服務無縫接軌。

以下 smart_open 讀檔的範例,完全與 Python 內建 open() 函式使用方式一致,因此不用擔心使用 smart_open 需要改寫既有程式的問題。

from smart_open import open


with open('/etc/hosts', 'r') as file:
    for line in file:
        print(line)

支援自動壓縮(compress)/解壓縮(decompress)

以往用 Python 寫入 gzip 壓縮檔(副檔名 .gz),會使用類似以下的程式碼進行:

import gzip
import shutil

# 以 gzip 方法壓縮檔案
with open('/home/user/file.txt', 'rb') as f_in:
    with gzip.open('/home/user/file.gz', 'wb') as f_out:
        shutil.copyfileobj(f_in, f_out)

或者使用 gzip 模組讀取 gzip 壓縮檔內容:

import gzip

# 讀取 gzip 壓縮檔
with gzip.open('/home/user/file.gz', 'rb') as f:
    file_content = f.read()

使用 gzip 模組寫入內容到 gzip 壓縮檔中:

import gzip


# 寫入資料到 gzip 壓縮檔
content = b'content here'
with gzip.open('/home/user/file.gz', 'wb') as f:
    f.write(content)

改用 smart_open 之後,都只要使用 open() 即可, smart_open 會自行偵測副檔名(file extension)推測壓縮方式,並結合 mode 參數判斷要自動進行壓縮或解壓縮,所以前述 gzip 相關的程式碼都可以簡化為:

from smart_open import open


# 讀取 gzip 檔
with open('/home/user/file.gz', 'rb') as f:
    file_content = f.read()

# 寫入 gzip 檔
content = b'content here'
with open('/home/user/file.gz', 'wb') as f:
    f.write(content)

目前 smart_open 支援 .bz2.gz 2 種副檔名的自動壓縮/解壓縮,如果你的檔案使用 bzip2gzip 壓縮,但是副檔名不是 .bz2.gz , 可以呼叫 open() 時指定 compression 參數(參數值為 .bz2.gz ),指名要使用何種壓縮/解壓縮方式:

from smart_open import open


# 讀取 gzip 檔
with open('/home/user/file.gz', 'rb', compression='.gz') as f:
    file_content = f.read()

# 寫入 gzip 檔
content = b'content here'
with open('/home/user/file.gz', 'wb', compression='.gz') as f:
    f.write(content)

如果檔案使用的是其他壓縮方式,也可以使用 register_compressor() 註冊其處理方式,以下是官方文件所提供的 register_compressor() 註冊 LZMA 壓縮方式的範例程式:

import lzma, os
from smart_open import open, register_compressor


def _handle_xz(file_obj, mode):
    return lzma.LZMAFile(filename=file_obj, mode=mode, format=lzma.FORMAT_XZ)


register_compressor('.xz', _handle_xz)


with open('smart_open/tests/test_data/1984.txt.xz') as fin:
    print(fin.read(32))

各式儲存服務整合

smart_open 最方便的是其整合多家雲端儲存方案,諸如:

  1. Amazon S3
  2. Google Cloud Storage
  3. Azure Blob Storage

以 Amazon S3 為例(需安裝 boto3 ),只要設定必要的環境變數(environment variables),例如 AWS_ACCESS_KEY_ID , AWS_SECRET_ACCESS_KEY 等等(詳見 Boto3 官方文件 ), smart_open 就會自動以 boto3 建立 S3 client 並透過 boto3 所提供的 API 與 S3 進行互動,接著你的 open() 就能夠直接讀取/寫入 S3 服務,例如官方所提供的範例:

from smart_open import open


with open('s3://commoncrawl/robots.txt', 'rb') as s3_object:
    for line in s3_object:
        print(line)

除了讓 smart_open 自動幫忙建立 S3 client 之外, smart_open 也提供 transport_params 參數讓開發者可以帶入自己建立的 boto3 S3 client:

import boto3

from smart_open import open


with open(
    's3://commoncrawl/robots.txt', 'rb',
    transport_params={
        'client': boto3.client('s3'),
    }
) as s3_object:
    for line in s3_object:
        print(line)

此外, smart_open 預設使用 S3 的 multipart upload, 可以將檔案切分成多個小區塊分開上傳,如此不僅可以改善傳輸效率與穩定度,也能在遭遇網路問題(network issue)導致上傳失敗時,僅重新傳送失敗的部分即可,很適合用來傳輸大檔案(例如檔案大小超過 100MB),但如果使用情境是上傳小檔案的話,可以關掉 multipart upload:

import boto3

from smart_open import open


with open(
    's3://<your S3 bucket>/<your object key>', 'wb',
    transport_params={
        'multipart_upload': False,
    }
) as s3_object:
    for line in s3_object:
        print(line)

以上是關於 Amazon S3 的部分,至於 Azure 以及 Google Cloud Storage 詳情請見 smart_openREADME 或者 Github repo 內的原始碼,基本上使用方法大同小異。

除了各家雲端儲存服務之外, smart_open 也支援以 HDFS(Hadoop Distributed File System), SSH, SFTP, SCP(Secure Copy Protocol) 等協定(protocol)進行讀寫檔案,因此可以在 URI(Uniform Resource Identifier) 上指定用前述協定存取檔案,也就是說 open() 也接受以下形式的路徑,真的十分方便:

hdfs://path/file
webhdfs://host:port/path/file
ssh://username@host/path/file
ssh://username:password@host/path/file
scp://username@host/path/file
scp://username:password@host/path/file
sftp://username@host/path/file
sftp://username:password@host/path/file

總結

以上就是關於 smart_open 的介紹,這麼好用的套件,還不快點導入到你的 Python 專案之中!

Happy coding!

References

https://github.com/RaRe-Technologies/smart_open