Posted at

Pythonでのimportとfromのあれこれ

More than 1 year has passed since last update.

今回の記事はPythonでよくやるimport周りでごちゃごちゃしがちなのでそれをまとめる感じになります。


import周りを解説する前の鉄則

さあimportとかfromのパスの関係をまとめようと思ったのですが、まず最初に鉄則を述べておきます。

それは実行はトップディレクトリからということです。


なぜか?

別にサブディレクトリから実行するというのもできないわけではありません。

ただ、それをするとディレクトリの構成がゴチャゴチャになり、処理の構造の視認性が格段に落ちるんです。

例えば、日本最強のベンチャーともうたわれるPFNが開発した機械学習用フレームワークであるChainerを見てみましょう。

Screen Shot 2018-06-23 at 16.25.43.png

これはトップディレクトリです。

ではこの中のChainerディレクトリの中を見てみましょう。

Screen Shot 2018-06-23 at 16.27.25.png

ご覧の通り、ディレクトリごとにOptimizerとか色々分かれてますよね?

こうするとコードが他人にとって非常に参照しやすくなります。

この中の__init__.pyを見てみるとこのようになっています。

Screen Shot 2018-06-23 at 16.30.13.png

各ディレクトリをimportしまくっていますね。

ディレクトリに__init__.pyを設置することなどについてはあとで触ります。

とりあえず今は基本はトップディレクトリから実行するということを頭に入れてくれれば問題ないです。


まずは基本から

とりあえずシチュエーションです。

今はhogeというディレクトリの中にいて、今からhoge.pyfoo.pyを並列させて作ります。

.

├── foo.py
└── hoge.py

という状況です。

hoge.pyの中には以下のようなクラスを構成置いておきます。


hoge.py

class Home(object):

def __init__(self):
self.text = "fuck Kyoto Univ"

def hoge(self):
print(self.text)

def hello():
print("Hi !")


一応あとで比較のために使うのでクラスじゃない普通の関数としてhello()関数も用意しておきました。

で、foo.pyでこのhoge.pyの中のHogeクラスをimportしたいと。

こういうときは簡単で、以下の通りになります。


foo.py

from hoge import Hoge

hoge_class = Hoge()

hoge_class.run()


そうするとちゃんとfuck Kyoto Univと表示されます。

ちなみにhello()関数は


foo.py

import foo

foo.hello()

とすればきっちり動いてくれます。


import先を一つ下のディレクトリに入れてみる

ではこのhoge.pyを一つ下のディレクトリに入れてみましょう。

シチュエーションとしては以下の通りです。

.

├── bar
│   └── hoge.py
└── foo.py

これをfoo.pyを叩きたいときは以下の通りにします。


foo.py

from bar.hoge import Hoge

hoge_class = Hoge()

hoge_class.run()


こうするとちゃんと実行できます。

hello()関数は


foo.py

from bar import hoge

hoge.hello()


だったり


foo.py

import bar.hoge as hoge

hoge.hello()


で動きます。

ではこうしてみるとどうでしょう?


foo.py

import bar.hoge.Hoge as Hoge

hoge_class = Hoge()

hoge_class.run()


importの方法を少し変えてみました。

Pythonを使っててNumpyを使うとき、みなさんよく

import numpy.random as random

a = random.randn()
print(a)

みたいなことやりますよね?

それと同様に、import bar.hoge.Hogeとしてみたわけです。

もちろんクラスじゃないhello()関数はちゃんと動いていました。

これを実行してみます。

Traceback (most recent call last):

File "foo.py", line 1, in <module>
import bar.hoge.Hoge as Hoge
ModuleNotFoundError: No module named 'bar.hoge.Hoge'; 'bar.hoge' is not a package

なんか怒られました。

あれれ?numpyのimportはできるのになんで?

そう思って困った人がいる思います。昔はぼくもそうでした。

ここで大切な教訓その1です。

importできるのはあくまでもパッケージであるということです。

実行できなかったとき、「bar.hogeはパッケージちゃうで」って言ってますよね。

これは、barはディレクトリで、hoge.pyはファイルであるから、別もんだということです。

じゃあわかった。bar.hogeはやめたるわ、と。

でもfromじゃなくてimport使ってみたい!ってこともあると思います。


foo.py

import bar

hoge_class = bar.hoge.Hoge()

hoge_class.run()


こんな感じに。

しかしできません。

以下のようなメッセージが出てきます。

Traceback (most recent call last):

File "/Users/komi/hoge/foo.py", line 3, in <module>
hoge_class = bar.hoge.Hoge()
AttributeError: module 'bar' has no attribute 'hoge'

じゃあどうしよう?

ここで登場するのが__init__.pyです!!

最初のchainerの例で出したように、__init__.pyにめっちゃimportさせてましたよね?

あれを使うことでこのbarをパッケージ化します。


__init__.py

from bar import hoge


もしくは


__init__.py

from bar.hoge import Hoge


とします。

この__init__.pyのおかげでbarディレクトリはパッケージということになり、importができるようになります。

とりあえずここまで読んでくればパッケージ化するということもなんとなくわかったのではないでしょうか?


もっと深いところに突っ込んでみる

ではこんなシチュエーションを考えてみましょう。

.

├── bar
│   └── ram
│   └── hoge.py
└── foo.py

この場合の解決策はもうわかりますよね?

__init__.pyを各階層に置いてあげればもうあとはimportやりたい放題です。

.

├── bar
│   ├── __init__.py
│   └── ram
│   ├── __init__.py
│   └── hoge.py
└── foo.py

ここで各init.pyの中身は


bar/__init__.py

from bar import ram



bar/ram/hoge.py

from ram import hoge


とすれば、各階層がパッケージ化されるわけです。


まとめ


  • importをするときはパッケージ化することが大切

  • 関数自体は呼び出すのは簡単だけどクラスを扱うときはちょっと気を使わなければいけない

  • 複雑な構成になる場合はなるべくトップディレクトリから実行するのが鉄則

といったところでしょうか?

まあ色々あると思いますが、コードを書くときに大切なのは

「他人に読ませる」ということではなく

「他人に読んでいただく」という姿勢ですね。

そのためにトップダウン形式にプロジェクトを構成して行くのがベストというわけです。

みなさん気を付けましょうね(特大ブーメラン)

お疲れ様でした!