pythonのimportエラー ValueError: Attempted relative import in non-package を雑に解決する

f:id:suganoo:20200423093424p:plain
pythonで悩ましいところの一つにimportエラーがあります。

適切な方法でモジュールをインポートする必要があるのですが、自分で作ったスクリプトを適当に相対パスでimportとしようとするとエラーになって悩むことがあります。

importのパスがよろしくないとこんな感じのエラーが出ます。

ValueError: Attempted relative import in non-package

実行したフォルダより上の階層はセキュリティ上の理由?からかアクセスできない制限があることをしらずなかなかエラーを解消できないことがあります。

参考サイト
teratail.com

そこで思った通りにimportできなかったけども、それを雑に解決した方法をブログに書いておこうと思います。

雑に解決した方法

サンプルコード

ファイル構成は下記のようになってるとします。

import_work/  <-- いまここで作業中
 - animal.py
 - script_main.py
 work_dir/
  - script_sub_1.py
  - script_sub_2.py
ここで同レベルの階層にいるanimal.pyとscript_main.pyについて

まずanimal.pyについては単純にprintするだけのクラスです

# coding: utf-8

class Animal:
  def call(self):
    print "Animal !!!"

それを同階層のスクリプトが実行します。

script_main.py

import animal

anml = animal.Animal()
anml.call()

これを実行すると

python script_main.py

Animal !!!

特に問題ないですね。

一つ階層が下がるとどうなるか?

これを一つフォルダを作ってスクリプトを書いてみます。

今ここで階層を確認しますが、script_sub_1.pyにとってanimal.pyは一つ上の階層になります。

import_work/
  - animal.py
  work_dir/
    - script_sub_1.py  <---

work_dir/script_sub_1.py

from .. import animal

anml = animal.Animal()
anml.call()

なので、冒頭に

from .. import animal

を入れているわけです。

そんでこれを実行してみるんですが、エラーになります。

import_work/   <--- ここで実行
  work_dir/
python work_dir/script_sub_1.py
Traceback (most recent call last):
  File "work_dir/script_sub_1.py", line 1, in <module>
    from .. import animal
ValueError: Attempted relative import in non-package

実行階層がよくなかったかな?と思ってwork_dir/ に移動して実行してみますが、同じようなエラーでした。

import_work/
  - animal.py
  work_dir/   <--- ここで実行
    - script_sub_1.py
python script_sub_1.py
Traceback (most recent call last):
  File "script_sub_1.py", line 1, in <module>
    from .. import animal
ValueError: Attempted relative import in non-package

ちなみに import_work/ で下記のようにimportを変えて実行してみてもエラーとなりました。

import_work/   <--- ここで実行
  - animal.py
  work_dir/
    - script_sub_1.py
import animal  # <---ここを変えた

anml = animal.Animal()
anml.call()
Traceback (most recent call last):
  File "work_dir/script_sub_1.py", line 4, in <module>
    import animal
ImportError: No module named animal

なぜimportできないか?

sys.path を調べてみる

ここでsys.pathを調べてみます。sys.pathはモジュールなどを調べるもとになるディレクトリのリストです。

import_work/
  - animal.py
  work_dir/
    - script_sys_path.py

script_sys_path.py

import sys
print sys.path

この実行結果が下記です。不要なところは伏せさせてもらいます。

['/home/suganoo/import_work/work_dir', ..., '....../python2.7/site-packages']

大事なところは/home/suganoo/import_work/work_dirなのですが、
script_sys_path.pyが存在するimport_work/work_dirフォルダしかパスが通ってないんですね。

=animal.pyが存在する/home/suganoo/import_work/にパスが通ってないわけです。

なので一つ上の階層のanimalモジュールが読み込めない。

自分も知らなかったのですが、sys.pathって実行してるファイルが存在する階層以降しか参照できないことがあらためて知りました。

一つ上の階層を入れる

ここで何とか一つ上の階層を入れ込んでみます。
import を少し変えたscript_sub_2.py を作ってみました。

import_work/
  - animal.py
  work_dir/
    - script_sub_2.py

script_sub_2.py

# coding: utf-8
import os
import sys

# 以降のimport のために一つ上の階層パスを追加
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../")

#----------------
import animal

anml = animal.Animal()
anml.call()

これを実行すると

python work_dir/script_sub_2.py
Animal !!!

ちゃんと意図通りに実行できてますね。

なにをやってるか

気になるのがsys.path.append...のところですよね。

sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../")
  1. __file__ は実行中のファイル名が入ります。なのでscript_sub_2.py
  2. os.path.abspath(__file__)は実行中ファイルの絶対パスが取得されます。→/home/suganoo/import_work/work_dir/script_sub_2.py
  3. os.path.dirname(os.path.abspath(__file__))は実行中ファイルのパスが取得されます。→/home/suganoo/import_work/work_dir/
  4. os.path.dirname(os.path.abspath(__file__)) + "/../"で一つ上の階層を指定します。→/home/suganoo/import_work/work_dir/../

そこまで取得できたパスをsys.pathに追加したのが下記になります。
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../")

これを実行したときのsys.pathは下記の通りです。

[...., '/home/suganoo/import_work/work_dir/../']

一番最後に指定した上の階層を指定するパスが含まれています。
なので実行できたわけですね。


どうしてこういう書き方にしたかというと
どこから実行してもそのファイルの一つ上のモジュールを実行できるようにしたかったんです。
そのフォルダに入らないと実行できない、というのはあまり使いやすいとは言えないでしょう。

このことを考えたのは、testsフォルダで単体テストスクリプトを書いてるときに実行する場所を制限されたくないな=どこからでも実行できるようにしたいなと思ったのがきっかけでした。

ただこのように一つ上の階層をsys.pathに追加するのは非推奨らしいので知っておきましょう。



このようにimportのエラーを無理矢理雑に解決してみたお話でした。


参考
okuya-kazan.hatenablog.com