毎回ハマる。マジで改修してほしいクソ機能。
成果物
背景
自作スクリプトtoken.py
を書いた。アクセストークンをファイルから取得するやつだ。しかし名前重複がおきて自作スクリプトの要素がインポートできなかった。標準ライブラリ(/usr/lib/python3.7/token.py
)と重複したのである。
- dir/
- src/
- token.py
- test/
- test_token.py
- src/
token.py
class Token: pass
test_token.py
# -------------------------------------------------- # ここでテスト対象の自作token.pyをインポートしたい! # -------------------------------------------------- import unittest if __name__ == "__main__": unittest.main()
実行
絶対パス、相対パスにかかわらずどこからでも実行できるようでありたい。テストからも呼び出したい。
/.../token.py ./token.py /.../test_token.py ./test_token.py
どうやってインポートすればよいか?
正解
test_token.py
import os, sys, pathlib sys.path.append(os.path.dirname(os.path.dirname(__file__))) print(sys.path) import src # OK from src import token # OK from src.token import Token #OK
ちなみにファイルパスはpathlib
を使って表現することもできる。だが、字数が大幅に短くなるわけでもない。むしろstr
なんて余計なメソッドを噛ませないとダメ。Python3.4以降でないと使えない。相対パスの指定方法を覚えていないとダメ。結局__file__
を渡す。など色々とクソ。
sys.path.append(str(pathlib.Path(__file__ + '/../..').resolve()))
sys.path.append(str(pathlib.Path(__file__).parent.parent.resolve()))
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
どちらがスマートか? 答え:どちらもクソ
私としては以下のように書きたかった。
sys.path.append(Here.Parent.Parent)
sys.path.append(Here.parent(2))
言うまでもなく、Pythonではこんなふうに書けない。
というか、そもそもこんなコード書きたくない。ファイル名で察してほしい。test_token.py
と書いたらtoken.py
を勝手にインポートしてほしい。むしろtest/
ディレクトリ配下はどんな名前であっても全部テストとして認識してほしい。
pytestとか使ったほうがいいのだろう。でも標準じゃないライブラリはできるだけ減らしたい。
失敗
test_token.py
import token # コレジャナイ! 標準ライブラリ(/usr/lib/python3.7/token.py)がインポートされてしまう!
自作のtoken.py
で定義したToken
クラスをインポートしたかったが失敗。
from token import Token # ImportError: cannot import name 'Token' from 'token' (/usr/lib/python3.7/token.py)
パスの位置が間違っていたので修正したつもり。
import ..src.token # SyntaxError: invalid syntax
from
で相対パスを書いてimport
でクラスなど要素を書いたつもり。
from .. import src.token # SyntaxError: invalid syntax
from
で相対パスを書いてimport
でクラスなど要素を書いたつもり2。
from ..src import token # ValueError: attempted relative import beyond top-level package
..
は使えない。test_token.py
を実行したらその親ディレクトリがルートになる。そしてルートより上は辿れないようだ。よって、実行ファイルでは..
が使えない。クソ仕様乙。
# 親ディレクトリをパスに追加する(相対パス) import os, sys, pathlib sys.path.append('../') print(sys.path)
以降、事前に上記のパス設定があるものとする。
import src # ModuleNotFoundError: No module named 'src'
import src.token # ModuleNotFoundError: No module named 'src'
from src import token # ModuleNotFoundError: No module named 'src'
from . import src.token # SyntaxError: invalid syntax
from .src import token # ModuleNotFoundError: No module named '__main__.src'; '__main__' is not a package
test.py
#!/usr/bin/env python3 # coding: utf8 import os, sys from unittest import TestLoader from unittest import TextTestRunner if __name__ == '__main__': TextTestRunner().run(TestLoader().discover(os.path.abspath(os.path.dirname(__file__))))
このファイルと同じ階層にあるすべてのtest*.py
ファイルを実行する。
ようするに全テストを実行する。
今回は1ファイルだけなのでべつにいらない。
所感
対象環境
- Raspbierry pi 4 Model B
- Raspberry Pi OS buster 10.0 2020-08-20 ※
- bash 5.0.3(1)-release
$ uname -a Linux raspberrypi 5.10.52-v7l+ #1441 SMP Tue Aug 3 18:11:56 BST 2021 armv7l GNU/Linux