condaとpip:混ぜるな危険

Anaconda環境下でpipを使う場合のリスクについて、日本語で書かれたページがほとんど見つからなかったので覚え書き。

追記 その2(2020-10-24)

1年越しですが補足記事書きました。以下の内容には2020年には当てはまらないものもいくつかあるので、ご注意ください。

追記 (2019-09-27)

予想以上にたくさんの方にこのエントリーを読んでいただけているようでありがとうございます。細かい表現を推敲したほか、Anacondaのドキュメントが全部リンク切れしていたので修正しました。また、SNS等での反応を見ていて一部誤解や認識違いがあるようなので後日補足エントリーを書こうと計画しています。

余談なのですが、個人ブログの記事って結構怪しい情報が多いです。ググると個人ブログ(とか各国のQ&Aサイト)が上位に出てくることが多く、それを見て満足してしまうことも多いかと思いますが、(私の記事を含め)こういった個人ブログは鵜呑みにはせず、公式ドキュメントの情報も参考にしたほうが良いと思います*1。自戒を込めて。

ひとことで

Anaconda下でpipを使うと予期せず環境が破壊され、最悪の場合Anaconda自体の再インストールが必要になる。pipは慎重に使いましょう。

condaとpip

pipPython環境で様々なパッケージを管理するための標準ツール。例えば、pip install numpyというコマンド一発で(依存関係も含め)PyPI(配布サイト)からnumpyをダウンロード・インストールすることができる。

一方、特にデータサイエンスのためにPythonを使う人に人気なのが、Anaconda, Inc.社の提供するAnacondaというPythonディストリビューション
www.anaconda.com
Anacondaにはcondaという独自のパッケージマネージャーが付属しており、仮想環境の管理やpipの代わりの役割などを果たしている。例えば、conda install numpyからnumpyをインストールできる。

なぜAnaconda?

筆者がPythonを使い始めた頃 (2013~2014年ぐらい)はPythonの公式側でパッケージ管理の仕組みがまだ整備途中の段階だった。特にWindowsではnumpyの様な(他言語で書かれたライブラリのコンパイルを伴う)パッケージをpipから入れるのは難しかった。そのため、非公式サイトからビルド済のファイルを逐一ダウンロードする必要があるなど不便が多かった。

Anacondaではその辺の不便が解消されており、当時から一発で必要なパッケージをすべてインストールできた(今となっては大半のパッケージがpipで入れられるように整備されている)。

condaの仕組み

condaとpipとでインストール等のコマンドはよく似ているのだが、両者のパッケージの仕組みは全く異なり、基本的に互換性が一切ない
また、condaでは複数の仮想環境下で同じパッケージをインストールする際、ハードリンクを利用することによって容量を節約している。これは大変便利な機能であるのだが、複数の仮想環境が完全に隔離されないというデメリットを有しており、特にbase環境を破壊してしまうと修復困難に陥る可能性がある。

pipとcondaの衝突

Anaconda下では基本的にcondaを使ってパッケージをインストールするのだが、一部のパッケージはconda installコマンドが用いるAnaconda社のレポジトリからは提供されていない。そのような場合にとるべきアプローチはいくつかある。

  1. デフォルト以外のチャネルからインストール(例: conda install -c matsci pymatgen, conda install -c conda-forge 〇〇)する。特にconda-forgeというチャネルには多数のパッケージがある。
  2. 自分でconda用のパッケージを作る
  3. pipを使ってインストールする。

このうち最後の「pipを使ってインストール」をするとcondaとpipのパッケージが混ざって厄介なことになる可能性がある。*2

pipを使って新たにパッケージをインストールする際、依存パッケージがcondaで既にインストールされている場合はあえて上書きされることは基本的にはない(はず)。しかしながら、

  • 依存関係のバージョン違い
  • condaとpipのパッケージ名の違い(例: pyqt (conda) vs. PyQt5 (pip))

等から予期せずcondaのパッケージが上書きされてしまうことがある*3。Pure Pythonのパッケージは上書きされたところで問題が発生しない場合が多いのだが、コンパイル済みのライブラリを含んだパッケージ(例えばnumpy)ではパッケージ内外でのライブラリの依存関係がおかしくなり、パッケージの使用に支障を来す場合がたまにある*4。その結果、パッケージ1つのインストールでAnaconda環境が壊れてしまうことがある。これがAnacondaのデフォルト環境(base環境)の場合、Anacondaそのものを再インストールしない限り修復困難になってしまう。また、condaではハードリンクを用いて仮想環境間でパッケージを共有しているため、一つの環境でやらかしてしまったが最後、base環境を含んだ全ての仮想環境が破壊されることもある。

PyQtの場合実際これが起こることが報告されている。こうなるとPyQt5をインポートしようとした瞬間、

ImportError: DLL load failed: The specified module could not be found.

というエラーが出たり、Python自体がSegmentation faultで強制終了・クラッシュしてしまう。こうなってしまうとPyQtをpipやcondaで再インストールしても修復不可能になる場合がある。(以下のGitHub上のIssue参照)
github.com
繰り返しになるが、直接PyQt5をインストールしなくても、入れようとしたパッケージのrequirementsの中にPyQt5が入っていると依存関係を満たすために予期せずpipがパッケージを上書きしてしまう場合がある。

どうすれば良いか

今までの説明からconda install X でパッケージが見つからなかった場合に安易にpipから入れるのは危険だということがわかる。リスクを減らすためには例えば次の様な手順を踏む(ちなみにAnaconda公式でもpipを使用する際の注意事項が挙げられている)。

  1. anaconda search X でXを提供しているチャネルがないか探す。あればconda install -c channel X等の方法でインストールする(この場合もチャネルの優先順位など、様々な注意が必要。詳しくは公式ドキュメント参照)。conda-forgeというチャネルは比較的しっかりしているので、あればそれがおすすめ。
  2. Xを提供しているチャネルがなかった場合、pipから入れるのが現実的である。その場合、
    1. まずPyPIのサイトから該当するパッケージを探し、依存関係を調べておく。依存するパッケージのうち、condaからインストール可能なものはcondaを用いてインストールしておく。
    2. 依存関係を満たしたらpip install XでXをインストールし、動作確認する。

あるいは別の選択肢として、

  • pipからしか入れられないパッケージを入れたい場合、新しいcondaの環境を作る(conda create -n env python)。その環境内ではconda installは一切用いない。なぜその環境内でconda installを用いないかというと、これをすると元のcondaの環境が(ハードリンクを通じて)汚染される可能性を排除できないから。
  • Anacondaを使うのをやめるPython公式サイトPythonを使い、パッケージはpipで導入する。仮想環境についてはvenvvirtualenvを用いる。

自分のconda環境は大丈夫?

既に構築済みの自分のconda環境でpipとcondaの衝突があるか確かめたい場合は、conda listを実行する。同じパッケージがpip経由とconda経由で入っている場合重複して表示される。何かがおかしくなっている可能性が高い(2019年6月にリリースされたconda 4.6.0以降のバージョンでは、condaのパッケージをpipで上書きしても重複表示されることがなくなったため、この方法は使えません)。

*1: そのために公式ドキュメントへのリンクを入れたのに全部リンク切れしていたという……

*2: ちなみに、このエントリーではcondaでインストール済みのパッケージをあえてpipで上書きしてしまうようなケース(これはポカミスと言って良いだろう)については言及していないが、その場合も同じような問題が発生する可能性がある。

*3: pip installは既存のファイルを上書きするときに何の警告も出さないし、インストールする前にどのパッケージが導入されるか確認する(dry-runに相当)ことも(おそらく)できない。

*4: condaだけ、pipだけを用いてもこの事象が発生する可能性がないわけではない。しかし、condaとpipを混ぜてしまった場合、両者が独立した異なる手段でパッケージングされていることから、問題が発生する可能性が高くなる。