概要
- ROSでpythonスクリプトを実行するとき、importの探索パスは
- 普通にpythonを使ったときのパス
- ROS packageのscriptディレクトリたちのパス
- になる。特に後者はどのように読み込まれているのか気になったので調べた。
環境
項目 | バージョン |
---|---|
OS | Ubuntu 18.04 |
ROS | melodic |
Python | 2.7.17 |
bash | 4.4.20 |
本題
- まずpythonのパスは
sys.path
に格納されており、print
(ROSならrospy.loginfo
など)で確認することが出来る。実際に確認してみると、- rosrunやroslaunchを実行したカレントディレクトリ
- スクリプトファイルがあるディレクトリ
- 環境変数
PYTHONPATH
で指定したディレクトリ - 標準ライブラリのためのディレクトリ
- pipでインストールしたライブラリが入っているsite-packagesなどのディレクトリ
- になっている。
- 今回調べたいROS packageのscriptたちは3つめの
PYTHONPATH
を経由して読み込まれている。しかし単純に実体が存在するディレクトリがPYTHONPATH
に羅列される仕組みにはなっていない。 - ではどうなっているかと言うと、
/opt/ros/${ROS_DISTRO}/setup.bash
や~/catkin_ws/devel/setup.bash
を読み込んだときにPYTHONPATH
に/opt/ros/melodic/lib/python2.7/dist-packages
や~/catkin_ws/devel/lib/python2.7/dist-packages
が追加される。 - それらのディレクトリの中にはビルドされたパッケージの名前のディレクトリたちが入っている。例えば
hoge
というpackageを作り、script/
にpythonスクリプトを配置してcatkin buildすると、以下のような内容の.../dist-packages/hoge/__init__.py
が自動生成される。
# -*- coding: utf-8 -*-
# generated from catkin/cmake/template/__init__.py.in
# keep symbol table as clean as possible by deleting all unnecessary symbols
from os import path as os_path
from pkgutil import extend_path
from sys import path as sys_path
__extended_path = '/your/ros/path/hoge/scripts'.split(';')
for p in reversed(__extended_path):
sys_path.insert(0, p)
del p
del sys_path
__path__ = extend_path(__path__, __name__)
del extend_path
__execfiles = []
for p in __extended_path:
src_init_file = os_path.join(p, __name__ + '.py')
if os_path.isfile(src_init_file):
__execfiles.append(src_init_file)
else:
src_init_file = os_path.join(p, __name__, '__init__.py')
if os_path.isfile(src_init_file):
__execfiles.append(src_init_file)
del src_init_file
del p
del os_path
del __extended_path
for __execfile in __execfiles:
with open(__execfile, 'r') as __fh:
exec(__fh.read())
del __fh
del __execfile
del __execfiles
- というわけで、これがimportされたときに読まれるので
pkgutil.extend_path()
によって実体が存在する箇所が無事読み込まれるわけだ。- pkgutilや
__init__.py
については参考に載せたリンクを参照。 - この仕組みになっているせいでVSCodeなどのIDEで開いたときにIDEが
__init__.py
までしか読みに行ってくれないのでちょっと面倒。
- pkgutilや
-
/opt/ros
以下のaptで入れたパッケージについても__init__.py
が置いてあるのは同じだが、こちらは自動生成とかではなくパッケージに実際にそのファイルが置いてある。- importされる実体もその階層に置いてある。
__init__.py
にはそれらのファイルをimportする内容が書いてあったり、簡単な実装なら直接書いてあったりする。 -
/opt/ros/${ROS_DISTRO}/share/packagename/scripts
以下にもpythonスクリプトが置いてあってrosrun
などで実行できるが、こちらにはnodeになるスクリプトが置いてあって、パスは通っていない(importはする必要もないし、できない)。
- importされる実体もその階層に置いてある。
おわりに
- これがわかったから何かできるのかというと何もできないが、勉強になった。
- 余談だが、最初
rosrun
やroslaunch
側でpythonを実行するときにパスが読み込まれていたりするのかと思ってそっちのソースを読みにいくなど迷走し、結構時間がかかってしまった。