2022/03/09

テクノロジー

【やさしく学ぶ】Pythonのパス解決について

この記事の目次

    はじめに

    皆さん、こんにちは!AIシステム部のS.Tです。

    開発をする上できちんと考えなければいけないパス問題。
    今回はPythonのパス解決法を考えてみました。

    パス解決とは?

    例えば次の例文を考えます。

    import hashlib
    import pandas

    このとき、importされる各モジュールは、実際にはディスクのどこかのファイルとして保存されています。
    どこのファイルから読み込まれたかは、__file__属性を参照すれば確認できます。

    print(hashlib.__file__)
    # => /home/xxxxx/.pyenv/versions/X.Y.Z/lib/pythonX.Y/hashlib.py
    # (おおもとのpythonのビルトインライブラリが読み込まれた)
    print(pandas.__file__)
    # => /home/xxxxx/projects/yyyyy/.venv/lib/pythonX.Y/site-packages/pandas/__init__.py
    # (pipenvによってプロジェクトルートに生成された仮想Python環境)

    このimport <ライブラリ名>と呼び出したときに、実際にはそのライブラリをどこから読み込むかを決める作業を「パス解決(Path Resolution)」といいます。

    パスには解決順がある

    Pythonは、ライブラリをインポートしたときに、あるきまった順番で、そのライブラリがないかを探しに行きます。
    その順番は、sys.pathの値を見ると確認できます。

    import sys
    sys.path
    [
      '',   # カレントディレクトリ
      '/home/xxxxx/.pyenv/versions/X.Y.Z/lib/pythonXY.zip',
      '/home/xxxxx/.pyenv/versions/X.Y.Z/lib/pythonX.Y',
      '/home/xxxxx/.pyenv/versions/X.Y.Z/lib/pythonX.Y/lib-dynload',
      '/home/xxxxx/projects/my-project/.venv/lib/pythonX.Y/site-packages'
    ]

    上記の例では、例えば import pandas と実行したときは、このリストの上から順番にpandasがあるかどうかを探しに行きます。

    解決順の決まり方

    このsys.pathですが、Pythonを実行する環境や、Pythonをインストールした場所によって変わります。では、どのようにしてその値が決まるかというと、Python起動時に、次の順番でsys.pathに先頭から値が追加されていきます。

    ① カレントディレクトリ(Pythonを起動したときに居た場所)
    +
    ② 環境変数PYTHONPATHの値
    +
    ③ インストール場所に依存するパス
    +
    ④ .pthによって追加されるパス

    ① カレントディレクトリ

    これはもうそのままで、無条件で先頭に追加されます。つまり、import hogehogeと実行したときに、まずはカレントディレクトリにhogehogeがないか確認しに行きます。

    ② 環境変数PYTHONPATHの値

    Pythonを起動する際に、環境変数PYTHONPATHにディレクトリのパスを設定しておくと、その場所を解決先の候補に加えることができます。加えられる位置は、①のカレントディレクトリの後です。

    $ export PYTHONPATH=/home/xxxxxx/my-packages
    $ python
    
    >>> import sys
    >>> sys.path
    ['', '/home/xxxxxx/my-packages', '/home/xxxx/.pyenv/versions/X.Y.Z/lib/pythonXY.zip', 
    ....(以下略)
    ]

    また、Linux系OSの場合は:(コロン)で、Windowsの場合は;(セミコロン)で区切ることで複数のパスを解決先の候補に加えることができます。

    $ export PYTHONPATH=/home/xxxxxx/my-packages:/home/xxxxxx/my-tools
    $ python
    
    >>> import sys
    >>> sys.path
    ['', '/home/xxxxxx/my-packages', '/home/xxxxxx/my-tools', '/home/xxxx/.pyenv/versions/X.Y.Z/lib/pythonXY.zip', 
    ....(以下略)
    ]

    個人開発したツールがどこか別の場所にあって、それを使いたい場合は、このオプションを使いましょう。

    ③ インストール場所に依存するパス

    これはPythonをOSにインストールした際に、どのパスにインストールしたかによって変わる値です。実行するPythonのバージョンや、Anacondaなどのリポジトリ、仮想環境の違いによって決まります。
    具体的には、sys.prefixが、今実行しているPythonのインストールされているディレクトリで、その値をもとに、次のようなパスが追加されていきます。

    <sys.prefixの値>/lib/pythonX.Y                # 主要ライブラリ(`os`とか`sys`とか)
    <sys.prefixの値>/lib/pythonX.Y/lib-dynload    # Cライブラリ
    <sys.prefixの値>/lib/pythonX.Y/site-packages  # サードパーティライブラリ

    ④ .pthによって追加されるパス

    これはあまり使うことはないと思いますが、site-packageディレクトリの子ディレクトリにもパスを通したい場合に、site-packageディレクトリ内に拡張子が.pthのファイルを置き、そこにパスを通したい子ディレクトリを書いておくと、それも解決先の候補に加えることができるというものです。

    試しに、hogeというディレクトリをsite-packagesに追加し、そこにパスを通してみます。

    $ echo "hoge" > /home/xxxx/.pyenv/versions/X.Y.Z/lib/pythonX.Y/site-packages/mypath.pth
    $ mkdir /home/xxxx/.pyenv/versions/X.Y.Z/lib/pythonX.Y/site-packages/hoge
    
    $ python
    
    >>> import sys
    >>> sys.path
    ['', 
    '..(中略)..', 
    '/home/xxxxxx/.pyenv/versions/X.Y.Z/lib/pythonX.Y/site-packages', 
    '/home/xxxxxx/.pyenv/versions/X.Y.Z/lib/pythonX.Y/site-packages/hoge']

    上記の通り、作成したhogeディレクトリにパスが通りました。

    ちなみに、.pthに追加したディレクトリが存在しない場合、sys.pathには追加されないようです。ちょっとしたやさしさを感じます。

    まとめ

    というわけで、パス解決について今回は詳しく見てみました。
    「あれ?正しくimportされない・・」と思った時に、どう対処すればよいかを考えるとき、ここで解説したことが役に立ってくれればと思います。

    おまけ

    当社のキャリアアドバイザーが「WEB事業会社に興味はあるけど、何から始めるべきかわからない」「転職は考えていないけど、今の環境が今後に活きるのかわからない」など、今後のために情報収集したい、という方に少しでも参考になる情報をまとめています。良ければご覧ください!

    • 2024/09/12

      WEB事業会社が中途採用で求めるエンジニアの特徴5選

      当社のCAが「WEB事業会社に興味はあるけど、何から始めるべきかわからない」「転職は考えていないけど、今の環境が今後に活きるのかわからない」など、今後のために情報収集したい、という方に少しでも参考になる情報をお渡しできればと思います。

      テクノロジー

    ※本記事は2022年03月時点の情報です。

    著者:マイナビエンジニアブログ編集部