Pythonパッケージ作りの初歩の初歩 その2
fijixfiji.hatenablog.com
前回(上のページ)に続いて、今回も、ライトなPythonユーザーがパッケージについていろいろ調べながら理解したことをまとめていきたいと思います。
今回のテーマは、setup.pyのsetupです。
主に読んだところはここ:
- 7. 使用例 — Python 3.6.4 ドキュメント
- 2. setup スクリプトを書く — Python 3.6.4 ドキュメント
- sampleproject/setup.py at master · pypa/sampleproject · GitHub
- Building and Distributing Packages with Setuptools — setuptools 38.5.1 documentation
pipを使っていて、いくつかの疑問がありました。
例えば、pip install pillow
でインストールした場合、実際にimportする場合は、from PIL import Image
といった感じで、pillowではなく、PILと指定したりします。pip installする時の名前と、importする時の名前が異なるパッケージがたまにある(scikit-learnならsklearnとか *1 )ので、なんでだろうというものです。
あと、自分でsetup.pyを書いた時に、py_modules
とpakages
とpackage_dir
と、importする時の名前がごっちゃになって、わかんないよーとなりました。
基本の「き」
配布物のパッケージを作るには、まず、作業するようのディレクトリを用意します。そしてその中に配布したいものと一緒にsetup.pyというファイルを用意します。
<root>/ setup.py 《その他いろいろなファイル》
setup.py
from setuptools import setup setup(name='hoge', version='1.0', ...略...
という感じになります。その他いろいろなファイルには、配布したい(モジュールコンテナとしての)パッケージや、パッケージ、拡張モジュール用のソースコード、データから、READEMEファイル、ライセンスのファイルなどなどを含めることができます。だいたい、必要なものは、決まっているので、(インターン向けに書いた)Pythonパッケージを作る方法 - Qiita(や GitHub - kennethreitz/samplemod: Sample module for Python-Guide.org.)を参考にするとだいたい間違わないと思います。
setup.pyの引数たち
setupの引数を全てではないですが、いろいろと見ていきます。Building and Distributing Packages with Setuptools — setuptools 38.5.1 documentation
まずは、name
について。
name
は、sampleproject/setup.py at master · pypa/sampleproject · GitHubの説明からいくと、"the name of your project"です。プロジェクトの名前。または、下記の引用にあるように、「配布物の名前」です。ここに記述してある名前が、pip install
の時に指定できます。あと、インストールしたあとに、pip list
したときに表示される名前になります。後述しますが、ここのname
はimportのときに使う文字とは(習わしでは一致しますが、基本的には)関係がありません。
配布物の名前は name オプションで個々に指定し、配布されるモジュールの一つと配布物を同じ名前にする必要はないことに注意してください (とはいえ、この命名方法はよいならわしでしょう)。ただし、配布物名はファイル名を作成するときに使われるので、文字、数字、アンダースコア、ハイフンだけで構成しなければなりません。
https://docs.python.jp/3/distutils/examples.html#pure-python-distribution-by-module
次に、version
について。
「バージョン番号は major.minor[.patch[.sub]] の形式をとるよう奨めます。」(2. setup スクリプトを書く — Python 3.6.4 ドキュメント)のように書きます。PEP 440(PEP 440 -- Version Identification and Dependency Specification | Python.org)もあるようなので、参照ください。
description
url
author
author_email
などなどを記述して行きます。
https://docs.python.jp/3/distutils/setupscript.html#additional-meta-dataや https://github.com/pypa/sampleproject/blob/master/setup.pyが参考になると思います。
どんなキーがあるかというと、Building and Distributing Packages with Setuptools — setuptools 38.5.1 documentationに一覧があります(多分公式のドキュメントがこれかな、きっと)。
で、続いて、本題のpy_modules
packages
package_dir
の3つについてです。
setup.pyの中でのモジュールやパッケージの指定方法についてですが、https://docs.python.jp/3/distutils/examples.htmlがかなりわかりやすいです。で、若干、つまずいたところとしては、利用する(importするとき)の記述とどういう関係になるのかという点でした。import mypkg
みたいなことをしたときに、どう指定するかという利用する側からの補足を入れてみたいと思います。
まず、7.1のモジュール形式という形です。(配布物としての)パッケージだけど、配布するものが(モジュールコンテナとしての)パッケージじゃなくモジュールのパターンです。
ディレクトリ構成:setup.pyと同じところにfooモジュールとbarモジュールが置いてある。
<root>/ setup.py foo.py bar.py
setup.py:
setup(name='foobarpkg', version='1.0', py_modules=['foo', 'bar'], )
foo.py:fooモジュール
def foo(): print('foooo!')
こういう状態で、インストールを行うと、pip listの中には、foobarpkg
が現れます。利用するときは、"モジュール"がインストールされているので、
import foo foo.for()
のように利用します。個人的にはちょっと驚いたところでした。一切にname
のfoobarpkg
が登場しません。fooモジュールとbarモジュールが、(配布物としてのパッケージの名前と関係なく)グローバルな名前で入ってしまいました。
続いて、「典型的なケース」です。(モジュールコンテナとしての)パッケージを配布する(配布物としての)パッケージを作るときです。
ディレクトリ構成:foobarディレクトリに、foobarパッケージ(fooモジュールとbarモジュール)がある。
<root>/ setup.py foobar/ __init__.py foo.py bar.py
setup.py:
setup(name='foobarpkg', version='1.0', packages=['foobar'], )
foo.py:foobar/fooモジュール
def foo(): print('foooo!')
pakages
で指定したので、foobarディレクトリをfoobarパッケージとしてインストールしてあります。構造的には、foobarパッケージの中にfooモジュールがあるので、使うときは、
from foobar import foo foo.foo()
または
import foobar.foo
foobar.foo.foo()
みたいに記述します。で、ここでもname
で指定したfoobarpkg
は登場しません。
このようにsetup.pyのpy_modules
かpakages
でパッケージかモジュールを羅列していけば、パッケージかモジュールを配布するパッケージを作成することができます。
で、だいたい原則がわかったので、いろいろ応用させることができます。foobarpkgパッケージ1つで、foobarパッケージとfooパッケージとbarパッケージの3つを配布することにしようと思うと、
<root>/ setup.py foobar/ __init__.py foo.py bar.py foo/ __init__.py foofoo.py bar/ __init__.py barbar.py
setup.py:
from distutils.core import setup setup(name='foobarpkg', version='1.0', packages=['foobar', 'foo', 'bar'], )
のようにすればよく、利用するときは、
from foobar import foo from foo import foofoo from bar import barbar
のように指定します。
2つ前の例で、「foobarディレクトリをfoobarパッケージとしてインストール」と書きましたが、packages
に書く文字列は、配布した際に使って欲しい(importで指定して欲しい)名前になります。パッケージの中のディレクトリ名とは限らない(デフォルトではそう解釈するみたいです)。
なので、「配布したいパッケージの名前(foobar) <---> foobarpkgパッケージ内でのそのパッケージがあるディレクトリ」(典型的な例では一致)の対応を決めることもできます。それが、pakage_dir
になります。package_dirの辞書は、キーがimportするときに使うパッケージ名、値が実際に存在しているディレクトリになります。
ややこしいですが、fooディレクトリのパッケージをbarという名前のパッケージとして、barディレクトリのパッケージをfooディレクトリのパッケージとして配布することもできます(一番簡単な例は、ドキュメントに書いてあるとおり、srcっていうディレクトリの中にあるパッケージに、foobarというパッケージ名をつけられる例だと思います。)
<root>/ setup.py src/ __init__.py foo.py bar.py foo/ __init__.py foofoo.py bar/ __init__.py barbar.py
from distutils.core import setup setup(name='foobarpkg', version='1.0', packages=['foobar', 'foo', 'bar'], package_dir={'foobar': 'src', 'foo': 'bar', 'bar': 'foo'} )
importするときは、
from bar import foofoo foofoo.foo()
のようになります。
が、こんな無駄な対応はしないほうが良いと思います。(あと、別件ですが、pip install -e ./foobarpkgって、別にpackage_dirを読んでくるわけじゃないの??ここは謎いが深まった・・・)
ドキュメントの例にあるルートディレクトリにパッケージの名前をつけたりするやつとかも、もう理解できるようになりました。
で、ちょっと脇道へ。
上記のようにパッケージした場合、関数を一つ呼び出すのに、import foobar
したあとにfoobar.foo.foo()
のようにモジュールを書くか、from foobar import foo
したあとにfoo.foo()
しなきゃいけなく、特に小さなパッケージの場合は、めんどくさいです。(つまり、モジュールみたいな感じで使いたいけど、パッケージなんですけどという状況)。その場合は、__init__.py
に一工夫加えると、import foobar
したあとに、foobar.foo()
のように呼び出せます。
__init__.py:(fooモジュールからfoo関数をインポートする)
from .foo import foo
です(多分)。このへんは、6. モジュール (module) — Python 3.6.4 ドキュメントとかを読むと、良いかもしれません。
もしも __all__ が定義されていなければ、実行文 from sound.effects import * は、パッケージ sound.effects の全てのサブモジュールを現在の名前空間の中へ import しません 。この文は単に(場合によっては初期化コード __init__.py を実行して) パッケージ sound.effects が import されたということを確認し、そのパッケージで定義されている名前を全て import するだけです。 import される名前には、 __init__.py で定義された名前 (と、明示的にロードされたサブモジュール) が含まれます。パッケージのサブモジュールで、以前の import 文で明示的にロードされたものも含みます。
6. モジュール (module) — Python 3.6.4 ドキュメント
配布したいPythonのスクリプトと、配布したパッケージの名前の対応関係がわかりましたが、他のも拡張モジュールとして配布する場合があると思います。
拡張モジュールは、プログラミング言語もPythonしか書けないライトなユーザー(僕)の場合、あまり縁がないと思っていましたが、Cythonを書くと、拡張モジュールを作ることになるので、簡単な例と参考になるリンクを書いておこうと思います。
- Welcome to Cython’s Documentation — Cython 0.28a0 documentation
- Cython ドキュメント(和訳) — Cython 0.17.1 documentation(日本語訳)
- 2. setup スクリプトを書く — Python 3.6.4 ドキュメント
Cythonのソースからだけで拡張モジュール作る場合などは簡単なのですが、Cのライブラリを使う場合ではリンクやコンパイルの時のオプションを指定する必要があります。
from distutils.core import setup from distutils.extension import Extension from Cython.Build import cythonize extensions = [ Extension("primes", ["primes.pyx"], include_dirs = [...], libraries = [...], library_dirs = [...]), # Everything but primes.pyx is included here. Extension("*", ["*.pyx"], include_dirs = [...], libraries = [...], library_dirs = [...]), ] setup( name = "My hello app", ext_modules = cythonize(extensions), )http://cython.readthedocs.io/en/latest/src/reference/compilation.html#configuring-the-c-build
のように、書きます。つまり、include_dirs
やlibraries
library_dirs
を指定します。
んで、このオプションがなんなのか理解しないといけないのですが、これは、Cのコンパイルとリンクの知識になるので、
あたりを読みつつ、なんとなく把握しました。
ちなみに、はまりポイントとしては、python - Cython compiled C extension: ImportError: dynamic module does not define init function - Stack Overflowにある通り、
第一引数の末尾のモジュール名と、Cythonのソースコードのファイル名を一致させないと、インポートがうまくできませんので注意です。つまり、
Extensiton("foobarpkg.foo", ["foo.pyx"], ....
みたいな感じです。
install_requires
について。
依存関係を記述します。ここを記述することで、依存するパッケージも勝手にpipさんが一緒にインストールしてくれます。
最後に、scripts
またはconsole_scripts
です。
flake8などのツールは、flake8コマンドで、シェルから実行できるようになっていたりします。
なので、シェルで動かすようのコマンドも配布することができます。
指定する方法は2つあり、scripts
に書くか、console_scripts
のentry_points
に書くかします。
わかりやすところとしては、Command Line Scripts — Python Packaging Tutorialがあります。
以上、今日は、ここまで。
setup.pyの引数として紹介していましたが、もろもろのパラメータは、
https://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-filesのように、setup.cfgに書くことも可能らしいです(あんまりよくわからん)
次回へ続く(?)
*1:ちなみに、 sklearn 0.0 : Python Package Indexというnameがついたものが存在していた笑