Python
Python3

__version__ ってアンチパターンでね?

More than 1 year has passed since last update.

Python のライブラリでは、よく __version__ が定義されていて、
インタラクティブシェルで、簡便なバージョン確認に使えますが、

>>> import pandas
>>> pandas.__version__
'0.21.0'

自分でライブラリを書くのに、__verson__ を定義するのはアンチパターンじゃないかと、最近思い始めました。

じゃあどうするの?

こうします。

  • __version__ は定義しない
  • バージョンは setup.py に(だけ)指定する

以下理由です。

理由1: 二重管理になる

バージョン番号は、setup.pyにも書かなくていけません。

# mylibrary.py
__version__ = '1.2.3'
# setup.py
from setuptools import setup
import mylibrary

setup(
  version='1.2.3',
  ...
)

ファイルを2つ編集するのは面倒ですね。
面倒だけならまだいいのですが、両者のバージョンがズレてしまったらどうするのか?バージョンを合わせるだけのために、新しいバージョンをリリースするのか?

理由2: setup.py が本体のコードに依存してしまう

二重管理を避けるために、setup.py で__version__を参照する方法もよく取られています。

# setup.py
from setuptools import setup
import mylibrary

setup(
  version=mylibrary.\_\_version\_\_,
  ...
)

しかしこれは、mylibrary がどこでも容易にimportできることを暗黙に仮定しています。もし、

  • mylibrary が内部で他のライブラリに依存していたら?
  • mylibrary がC言語拡張モジュールだったら?
  • mylibrary が import にすごく時間がかかる巨大なライブラリだったら?

特に1番目のケースでは、依存ライブラリのインストールに setup.py を実行しなけりゃならないが、mylibrary が import できないので setup.py が実行できない!

って状況になります。

理由3: 無くても困らない

そんなにバージョンを知りたいですか?

別に、バージョンを知りたければ、pip freeze コマンドでよくないですか?

$ pip freeze | grep mylibrary
mylibrary==1.3.4

依存ライブラリのバージョンごとに振る舞いを変えるために、こんなポリフィルを書くことがありますが、

if mylibrary.__version__ >= '1.4.0':
   something = mylibrary.something
else:
   def something():
      mylibrary.something のポリフィル

しかし、そもそもこれは適切なのか?

バージョン関係の処理は ライブラリの責務なのでしょうか?。
あんまり頑張るのではなく、単に古い依存ライブラリは切ってしまう。それが、古いライブラリの更新を促し、結果的にみんなが幸せになるのではないかとも思います。

なお、どうしてもプログラム的にバージョンを取りたいなら、pkg_resources を使うこともできます。

import pkg_resources
v = pkg_resources.get_distribution('mylibrary').version

理由4: そもそも標準でもなんでもない

__version__ はなんとなくみんな定義しているだけで、定義しなくてもよいものです。「__version__ を追加してください」ってIssueは建てられるかもしれませんが。

標準では無いので、バージョンを確実に取得したいなら、上述のpkg_resources を使わざるをえません。そして、pkg_resources では __version__ は不要です。

感想

SQLアンチパターンみたいな本を読むと、アンチパターンって、「簡単にできる」とか「一見シンプル」なものが多いような気がするんですが、__version__ はまさにそれだなって思います。初心者でも簡単にできるし、一見シンプルで賢そうな解決。でも後々困る。