3
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

ROSでpythonのパスがどのようにセットされているのか調べた

概要

  • 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までしか読みに行ってくれないのでちょっと面倒。
  • /opt/ros以下のaptで入れたパッケージについても__init__.pyが置いてあるのは同じだが、こちらは自動生成とかではなくパッケージに実際にそのファイルが置いてある。
    • importされる実体もその階層に置いてある。__init__.pyにはそれらのファイルをimportする内容が書いてあったり、簡単な実装なら直接書いてあったりする。
    • /opt/ros/${ROS_DISTRO}/share/packagename/scripts以下にもpythonスクリプトが置いてあってrosrunなどで実行できるが、こちらにはnodeになるスクリプトが置いてあって、パスは通っていない(importはする必要もないし、できない)。

おわりに

  • これがわかったから何かできるのかというと何もできないが、勉強になった。
  • 余談だが、最初rosrunroslaunch側でpythonを実行するときにパスが読み込まれていたりするのかと思ってそっちのソースを読みにいくなど迷走し、結構時間がかかってしまった。

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
3
Help us understand the problem. What are the problem?