Python3 (3.6.5)でオレオレモジュールを作成してインポートしようとした際に結構ハマったのでメモ
importの基本
import <module>
キーワードでモジュールをインポートする
import math print( math.pi ) # => 3.141592653589793
モジュールのメソッドはmodule.method
で実行する
from <module> import <method>
でインポートするとモジュール名を都度記述しなくてもメソッドの呼び出しができる
from math import pi, sqrt print( sqrt(9) ) # => 3.0
from ... import ...
でインポートする際にメソッド名が被ると、後からインポートしたものが有効になる
from math import pi from my_modules import pi pi # => my_modules.pi
一度インポートされたモジュールは再インポートされない
モジュール echo.py
# echo.py print( f'echo.py: name is {__name__}' )
実行ファイル base.py
# base.py import echo # => echo.py が実行される print('---') import echo # => 再実行はされないので何も出力されない
実行↓
$ python base.py echo.py: __name__ is echo ---
同じ階層にあるディレクトリ内のファイルをインポート
フォルダ構成
/root |- /modules | |- echo.py |- base.py
# modules/echo.py def say_name(): print("I'm " + __name__)
import <ディレクトリ>.<ファイル名>
でインポート
# base.py import modules.echo modules.echo() # => I'm modules.echo
from <ディレクトリ> import <ファイル名>
でインポート
# base.py from modules import echo echo.say_name() # => I'm modules.echo
import <ディレクトリ>.<ファイル名>
・from <ディレクトリ> import <ファイル名>
でのインポートはどの階層からbase.py
を実行しても問題なくインポートすることができるようです。実行位置ではなくファイルから相対パスで読み込まれるという認識で良いのでしょうか?(この部分正確にな挙動はまだ理解できていません)
$ python root/base.py > "I'm modules.echo" $ cd root $ :root python base.py > "I'm modules.echo" $ cd modules $ :modules python ../base.py > "I'm modules.echo"
更に深い階層にあるファイルのインポート
フォルダ構成
/root |- /modules | |- /sub | |- echo.py |- base.py
import ...
- ディレクトリを.
で繋げる
# base.py import modules.sub.echo modules.sub.echo.say_name() # => I'm modules.echo
from ... import ...
- fromの後に.
区切りでディレクトリを指定する。
# base.py from modules.sub import echo echo.say_name() # => I'm modules.echo
from ... import ...
でimportの後にディレクトリを.
区切りで表記するとエラー
# base.py from modules import sub.echo # => from modules import sub.echo # => ^ # => SyntaxError: invalid syntax
実行ファイルの上の階層にあるファイルのインポート
フォルダ構成
. |- /root | |- base.py |- /modules | |- echo.py
Pythonでは実行ファイルより上の階層にアクセスできない!?
モジュールのインポートで階層を.
で表しているのだと思い、次のようにしてみた所エラーになってしまいました。
# base.py from ..modules import echo # => ValueError: attempted relative import beyond top-level package
# base.py import ..modules.edho # => SyntaxError: invalid syntax
どうやらPythonは現在実行されているファイルをモジュールのルートとみなすという仕様があり、その結果ルートより上の階層が見えなくなっていてるためエラーになるようです。
sys.path にモジュールのあるパスを追加するとインポートできる
sys.path
にはモジュールを検索するパスのリストが入っており、ここにインポートしたいファイルのあるパスを追加しておけば、実行ファイルの上の階層であってもインポートすることが出来るようになるみたいです。
# root/base.py import sys sys.path.append('../modules') print(sys.path) import echo
どうやらsys.path
にあるパスは実行した位置を起点にして探すようで、上記のように実行ファイルからの相対パスで指定した場合/root
ディレクトリ以外で実行するとエラーになってしまいます。
$ python root/base.py > ['/Users/.../root', '/User/.../.pyenv/versions/3.6.5/lib/python3.6', ... , '../modules'] # ↑ sys.path に '../modules' がそのまま入っている > ... > import echo > ModuleNotFoundError: No module named 'echo'
パスの問題を解決するには、環境依存にならないよう動的に絶対パスを取得する方法があれば良さそうです。
pathlib でモジュールの絶対パスを取得する
Python 3.4 以上ではpathlib
を使って絶対パスを取得するのが良さそうです。
# root/base.py import sys import pathlib # base.pyのあるディレクトリの絶対パスを取得 current_dir = pathlib.Path(__file__).resolve().parent # モジュールのあるパスを追加 sys.path.append( str(current_dir) + '/../' ) # モジュールのインポート import echo echo.say_name() # => I'm echo
pathlib
で取得できるパスはpathlib.PosixPath
オブジェクトなので、そのままパスを編集しようと文字列連結しようとするとエラーになってしまいます。文字列連結などでパスを編集する際にはstr()
で文字列にする必要があるのがハマりどころかもしれません。
これで実行ファイルの上の階層のファイルも読み込めるようになりました。
node.jsやruby・phpにくらべて、自分の位置の絶対パスの取得や上の階層のファイルのインポートが面倒だな〜という印象を持ちました。何かしら意味があって実行ファイルをrootとして上の階層にアクセスできなくしている経緯があると思うのですが、そのあたり詳しく説明されているサイトを見つけることが出来なかったので、少しもやっとしています。
それと、上の階層のファイルを読み込むためにsys.path
にモジュールのあるディレクトリのパスを渡すという方法がお作法的に良いのかどうかいまいち判断できていません。
お作法的なものがあれば知りたいです。 (このドキュメント読めとかあればお願いします。)
また__init__.py
などでディレクトリ内のモジュールをまとめてインポートとか出来るようなので、そのあたりは追々試してしていきたいと思います。
[参考]
- Python 3でのファイルのimportのしかたまとめ - Qiita
- Python 3:sys.pathとos.pathについて(機械学習,手書き数字認識) - Qiita
- Pythonでディレクトリの上層にあるモジュールをimportするときの注意点 - 長時間睡眠記
- Pythonで型を取得・判定するtype関数, isinstance関数 | note.nkmk.me
- 作者: 森見登美彦,くまおり純
- 出版社/メーカー: 角川書店(角川グループパブリッシング)
- 発売日: 2012/11/22
- メディア: 文庫
- 購入: 17人 クリック: 459回
- この商品を含むブログ (85件) を見る
森見登美彦作品好きなので映画化うれしい。