Python で自作パッケージを作る際に、ソースコード以外のファイルを含めたくなる場合がある。 例えば Web アプリケーションを作るときの HTML テンプレートなんかはよくあるパターン。 今回は自作パッケージにソースコード以外のファイル (データファイル) を含めるやり方について書いてみる。
使用する環境は次の通り。
$ python --version Python 3.5.1 $ pip list pip (7.1.2) setuptools (18.2) wheel (0.24.0)
自作パッケージを用意する
データファイル云々を説明する前に、まずはそれを入れるためのパッケージから作っていこう。 今回作成するパッケージは 'mypackage' という名前にする。
最初にプロジェクトのディレクトリを用意する。 作成するパッケージ名とプロジェクトのディレクトリ名は揃える場合が多い。
$ mkdir mypackage
$ cd mypackage
プロジェクトのディレクトリに移動したら、さらに同じ名前でディレクトリを作る。 これが Python パッケージになるディレクトリだ。
$ mkdir mypackage
作成したディレクトリの配下に init.py という名前のファイルを作る。 これで mypackage ディレクトリが Python のパッケージとして認識できるようになる。 ファイルには動作確認用に greet() という名前の関数をひとつだけ用意しておこう。
$ cat << 'EOF' > mypackage/__init__.py #!/usr/bin/env python # -*- coding: utf-8 -*- def greet(): print('Hello, World!') EOF
次に mypackage のセットアップスクリプト (setup.py) を用意する。 このファイルを用意することで自作の Python パッケージを pip コマンドなどから扱えるようになる。
$ cat << 'EOF' > setup.py #!/usr/bin/env python # -*- coding: utf-8 -*- from setuptools import setup from setuptools import find_packages def main(): description = 'mypackage' setup( name='mypackage', version='0.0.1', author='example', author_email='[email protected]', url='www.example.jp', description=description, long_description=description, zip_safe=False, include_package_data=True, packages=find_packages(), install_requires=[], tests_require=[], setup_requires=[], ) if __name__ == '__main__': main() EOF
これで mypackage パッケージが完成した。 ここまででディレクトリ構成は次のようになっている。
$ tree . ├── mypackage │ └── __init__.py └── setup.py 1 directory, 2 files
自作パッケージをインストールする
それでは先ほど作ったセットアップスクリプトを使ってパッケージをインストールしよう。 パッケージのインストールにはスクリプトのサブコマンドとして install を指定する。
$ python setup.py install ...(省略)... Processing dependencies for mypackage==0.0.1 Finished processing dependencies for mypackage==0.0.1
pip コマンドでインストール済みのパッケージを見ると mypackage がインストールされたことがわかる。
$ pip list | grep -i mypackage mypackage (0.0.1)
インストールされたパッケージが使えることを確認しておこう。 Python からパッケージをインポートして動作確認用の関数を呼び出してみる。
$ python -c "import mypackage; mypackage.greet()"
Hello, World!
ばっちりだ。
ソースコード配布物を作る
ここからは説明の都合上、ソースコード配布物を使うことにする。 パッケージのソースコード配布物は sdist サブコマンドで作成できる。
$ python setup.py sdist ...(省略)... Writing mypackage-0.0.1/setup.cfg Creating tar archive removing 'mypackage-0.0.1' (and everything under it)
コマンドを実行すると dist ディレクトリにソースコード配布物の tar ball ができている。
$ ls dist | grep tar.gz$ mypackage-0.0.1.tar.gz
できたソースコード配布物を適当な場所に展開しよう。
$ tar xvf dist/mypackage-0.0.1.tar.gz -C /tmp
中身はこんなかんじになっている。 あまり詳しくは説明しないものの、元のセットアップスクリプトとパッケージ以外にもパッケージ情報を格納したファイルなどが入っている。
$ tree /tmp/mypackage-0.0.1 /tmp/mypackage-0.0.1 ├── PKG-INFO ├── mypackage │ └── __init__.py ├── mypackage.egg-info │ ├── PKG-INFO │ ├── SOURCES.txt │ ├── dependency_links.txt │ ├── not-zip-safe │ └── top_level.txt ├── setup.cfg └── setup.py 2 directories, 9 files
ソースコード配布物にデータファイルを含める
さて、ここから今回の本題に入っていく。 まずはパッケージというかソースコード配布物に含めたいデータファイルを用意しよう。 例として greeting.txt という名前のテキストファイルを作ることにする。
$ cat << 'EOF' > greeting.txt Hello, World! EOF
さて、この状態で改めてソースコード配布物をビルドして展開してみよう。
$ python setup.py sdist $ tar xvf dist/mypackage-0.0.1.tar.gz -C /tmp
先ほど作ったデータファイルが展開されたディレクトリに含まれるか調べてみよう。
$ find /tmp/mypackage-0.0.1 -name "greeting.txt"
残念ながら見つからない。 何もしない状態ではデータファイルはソースコード配布物に含まれないようだ。
では、どうすればデータファイルがソースコード配布物に含まれるようになるのだろうか。 これには MANIFEST.in という名前をもった設定ファイルが必要になる。 先ほど作った greeting.txt を含めるように include で指定しよう。
$ cat << 'EOF' > MANIFEST.in include greeting.txt EOF
改めてソースコード配布物をビルドして展開し直す。
$ python setup.py sdist $ tar xvf dist/mypackage-0.0.1.tar.gz -C /tmp
すると今度は展開されたディレクトリの中に greeting.txt が見つかった!
$ find /tmp/mypackage-0.0.1 -name "greeting.txt" /tmp/mypackage-0.0.1/greeting.txt
パッケージ内にデータファイルを含める
先ほどはソースコード配布物の中には含めたものの Python パッケージの中に入れていなかった。 次はデータファイルをパッケージ内に作るパターンを確認しておこう。
パッケージ内に data という名前でディレクトリを作って、そこに先ほどの greeting.txt を移動させよう。
$ mkdir -p mypackage/data $ mv greeting.txt mypackage/data
次は MANIFEST.in の指定もちょっと工夫してみる。 具体的には recursive-include を使うことで特定のディレクトリ以下の内容を再帰的に検索して含めるようにする。 例えば次の設定では mypackage/data に含まれるすべての内容が含まれるようになる。
$ cat << 'EOF' > MANIFEST.in recursive-include mypackage/data * EOF
ディレクトリ構成としては次のようになる。 pycache というディレクトリは先ほどソースコード配布物をビルドした際にパッケージの内容がコンパイルされたためにできた。
$ tree mypackage mypackage ├── __init__.py ├── __pycache__ │ └── __init__.cpython-35.pyc └── data └── greeting.txt 2 directories, 3 files
この状態で改めてソースコード配布物をビルドして展開し直そう。 先ほど展開した内容を途中で一旦削除している点に注意する。
$ python setup.py sdist $ rm -rf /tmp/mypackage-0.0.1 $ tar xvf dist/mypackage-0.0.1.tar.gz -C /tmp
ソースコード配布物を展開した内容を調べるとパッケージ内に greeting.txt が含まれていることがわかる。
$ find /tmp/mypackage-0.0.1 -name "greeting.txt" /tmp/mypackage-0.0.1/mypackage/data/greeting.txt
データファイルがインストールされることを確認する
データファイルがソースコード配布物に含まれることは確認できたので、次はそれが実際にインストールされることを確認しよう。 まずは最初にインストールした内容 (データファイルが含まれていなかった) をアンインストールした上で、もう一度パッケージをインストールし直す。
$ pip uninstall -y mypackage $ python setup.py install
この状態でサードパーティ製のパッケージがインストールされるディレクトリに移動する。 具体的には Python の site-packages というディレクトリになるんだけど、その場所を取得してそこに移動するには次のようにする。
$ cd $(python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")
インストールされた内容を確認すると、ちゃんと data ディレクトリと greeting.txt ファイルが格納されていることがわかる。
$ tree mypackage-0.0.1-py3.5.egg mypackage-0.0.1-py3.5.egg ├── EGG-INFO │ ├── PKG-INFO │ ├── SOURCES.txt │ ├── dependency_links.txt │ ├── not-zip-safe │ └── top_level.txt └── mypackage ├── __init__.py ├── __pycache__ │ └── __init__.cpython-35.pyc └── data └── greeting.txt 4 directories, 8 files
インストールされたデータファイルを扱う
さて、データファイルがインストールされたことは確認できたので、次は実際にそれを使ってみよう。
インストール済みのデータファイルにアクセスするには、標準ライブラリであれば pkgutil というモジュールを使う。 pkgutil.get_data() 関数にパッケージ名とファイル名を指定するとその内容がバイナリで取得できることがわかる。
$ python >>> import pkgutil >>> pkgutil.get_data('mypackage', 'data/greeting.txt') b'Hello, World!\n'
あとは取得したデータを煮るなり焼くなり好きにすれば良い。
ちなみに、これは setuptools の pkg_reources モジュール経由でも取得できる。
>>> import pkg_resources >>> pkg_resources.resource_string('mypackage', 'data/greeting.txt') b'Hello, World!\n'
めでたしめでたし。
スマートPythonプログラミング: Pythonのより良い書き方を学ぶ
- 作者: もみじあめ
- 発売日: 2016/03/12
- メディア: Kindle版
- この商品を含むブログを見る