Pythonパッケージでは、ファイル構成 != API
原文は Ben Hoyt により に公開されました。 このブログを購読する
このミニ記事は、PythonのAPI設計について私が行ったトークの一部に基づいています。
Pythonパッケージを作成する際の嬉しい点の一つは、パッケージディレクトリに.pyソースファイルを追加するだけで、ユーザーがすぐにimportできるようになることです。
例えば、tekstというテキスト処理パッケージを作成していて、Tokenクラスとtokenize関数を追加したいとします。当然ながら、それらをtekst/tokenizer.pyに実装するでしょう。
# tekst/tokenizer.py
@dataclass
class Token:
kind: str
content: str
def tokenize(text: str) -> Iterable[Token]:
...ここまでは順調です。しかし、ユーザーに次のような使い方をさせてはいけません。
import tekst.tokenizer
def process(token: tekst.tokenizer.Token):
...
tokens = tekst.tokenizer.tokenize('Hello, world')
for token in tokens:
process(token)テキストライブラリが巨大でもない限り、tekst.tokenizer.Xのような記述はすべて不要で冗長です。もちろん、ユーザーは次のように書くこともあるでしょう。
from tekst.tokenizer import Token, tokenize
def process(token: Token):
...
tokens = tokenize('Hello, world')
for token in tokens:
process(token)こちらの方がずっと見やすくなります。
しかし、ここでまた別の問題が生じます。ユーザーのコードが数行以上になると、Tokenやtokenizeがどこから来たのかが分かりにくくなります。ローカルで定義されたものなのか、それともどこかのモジュールからimportしたものなのか。もしそうなら、どのモジュールからでしょうか。確認するにはファイルの先頭までスクロールしてimport文を探すか、IDEのナビゲーション機能を使うしかありません。
しかし、これを簡単に解決する方法があります。パッケージをimport lib ... lib.Thing()という形で使えるように設計するのです。パッケージの__init__.pyでTokenとtokenizeをimportすればよいのです。次のようにします。
# tekst/__init__.py
__all__ = ['Token', 'tokenize', ...]
from .tokenizer import Token, tokenizeそうすれば、ユーザーはコードを比較的簡潔に保ちつつ、ライブラリ名を名前空間の接頭辞として残せるため、どこから来たものかが明確になります。
import tekst
def process(token: tekst.Token):
...
tokens = tekst.tokenize('Hello, world')
for token in tokens:
process(token)
...Zen of Pythonにもあるように、「Flat is better than nested(平坦な方が入れ子より良い)」そして「Namespaces are one honking great idea – let’s do more of those!(名前空間はとびきり素晴らしいアイデアだ――どんどん活用しよう!)」です。
しかし、そうすることでZen of Pythonに従うだけでなく、ファイル構成をパッケージのAPIから切り離すことにもなります。ユーザーはimportしやすく読みやすい優れたAPIを手に入れることができ、ライブラリ作者であるあなたは、実装を好きなファイルに自由に配置できるのです。
記事をランダムに読む
コメント
ログインしてコメントする