📝

sphinxを入れるために__init__.pyを後から入れたら大変な事になったので、今からでも気を付けることをまとめる

2023/08/29に公開

https://qiita.com/items/11042943f918fdd5a37c


お詫び

Qiitaの元記事にて、区切り線を「---」で書いている場所があり、これがZennの記法に干渉して一部うまく表示できない記事がある事を認識しています。
全ての記事を精査しきれていないため、お手数ですがお見かけの際は教えていただけると大変喜びます。


基礎的な話ばかりだけど、「こういう場合ってどうなんだっけ?」って頻繁に悩んでgoogle先生に教えを乞うので、この機会にきちんと整理!
コーディング規約から保守から、雑ながら包括的に。

ちょっとだけsphinxの導入とか、パッケージを意識したモジュールづくりの話とかもします。

sphinxを入れるために__init__.pyを後から入れたら大変な事になったので、今からでも気を付けることをまとめる

ファイル構成

今回の環境はこういう形で作ってます。

(root)
├── README.md
├── run.py
├── app
│   ├── __init__.py
│   ├── main.py
│   ├── libs
│   │   ├── __init__.py
│   │   ├── modules.py
│   │   ... py
│   └── src
│       ├── __init__.py
│       ├── modules.py
│       ... py
└── sphinx
    ├── Makefile
    ├── conf.py
    ├── make.bat
    ... rst

元々は__init__.pyなんてなかったし、appとかsphinxとかのディレクトリも後付けです。
出力先の階層が一段深まってしまったので、パスもそれぞれ書き直すことになってしまいました。
実装に着手する前に変更に強い仕組みを考えておくのは非常に重要な事だと改めて思いました。

__init__.pyについて補足

なにかモジュールが呼ばれた場合(たとえばapp/main.py)、同階層の__init__.py(app/__init__.py)が実行するより先に呼ばれるため、
後述のapp/src/modules.pyの from ..libs import modulesも1:app/__init__.pyを通って2:app/libs/__init__.pyを通ってlibs/modules.pyが実行されます。

これを検証する場合、各ファイルの先頭にprint __file__とすると流れをつかみやすいが、上の通り何度も呼ばれるので、呼ばれた回数を正しく追う方が大事。
つまり、sphinxのビルド時にエラーになると呼ばれた回数だけエラー件数が増えるので、__init__.pyには基本的には何も書かないようにします。

(current)

ここには__init__.pyを置く必要はありません。
パッケージの外から実行した時のイメージです。

"""
パッケージの外なのでfrom .app import mainとしない
"""

import app.main

(current)/app

sphinx-apidocのソーススクリプトで指定するパッケージ(ディレクトリ)
サブディレクトリ含む以下がドキュメントとして生成される

"""
srcのモジュールを参照して何らかの処理をする
"""

from .src import modules

(current)/app/src

"""
ここは普通に書く
main.pyから呼び出されて、libs/modules.pyを呼ぶ
"""

from ..libs import modules
# ~なにかの処理

(current)/app/libs

"""
ここは普通に書く
src/modules.pyから呼び出される
"""

# ~なにかの処理

ハマりどころ

当たり前だがモジュールはimportされたその瞬間、その場所から当該ファイルが1行ずつ処理されます。
この時、相対的なパスで通ってきた__init__.pyは通るたびに呼ばれるので、夥しい量のエラーメッセージを吐き出す可能性が高く、デバッグ時に血反吐を吐くことになります。
当然、__init__.pyに同階層のモジュールをimportして、同階層のモジュールが巡り巡って…なんて無限ループが発生しうる可能性も考えられます。

うっかり忘れがちですが、python3では暗黙的な相対パスでのimportは禁止。
__init__.pyがなくてもimportができるので、本当に忘れがち。
sphinxを入れると必須になるので、この問題も未然に防げます。

sys.path.appendを使う方法も考えられるものの、相対パスが使えるならそちらを選択したいですね。

このあと、WLSで生成したsphinxドキュメントがwindows10の権限回りでハマったのですが、別記事にて。

参考

読了後いいね!をお願いします。

どれだけの方に読んでもらっているか知りたいので、お手数をおかけしますがご協力いただけると嬉しいです。

GitHubで編集を提案

Discussion