pip install --upgrade pip (10.0.0) 後の奇妙な挙動について

TL;DR

pipを10.0.0に更新した後に

~$ pip
Traceback (most recent call last):
  File "/usr/bin/pip", line 9, in <module>
    from pip import main
ImportError: cannot import name main

というエラーが出た場合の対処方法をこちらにまとめた。

こちらの記事で言及したが、pipの挙動がちょっとおかしい。少し調べてみたところ原因がわかったので共有する。一応概要も載せておく。

概要

debian9でaptでpipを導入したあとに pip install --upgrade pip を行った場合、pipが使えなくなる。具体的には

~$ pip
Traceback (most recent call last):
  File "/usr/bin/pip", line 9, in <module>
    from pip import main
ImportError: cannot import name main

のようにエラーがでて、pipコマンドが使えなくなる。
また、python-pip を消して手動でインストールしても pip install でパッケージがインストールされない。debian9またはubuntu17.10を使用しているユーザーは特にハマりやすい。

原因

それぞれの現象の原因を詳しく示す。

ImportErrorの原因

aptでインストールした場合は9.0.1がインストールされるが、pipの最新版は10.0.0である (リリースされたのはつい先日のことである) 。
9系のpipスクリプトの内容は次のようになっている。

pipでインストールしたもの

#!/usr/bin/python

# -*- coding: utf-8 -*-
import re
import sys

from pip import main

if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
    sys.exit(main())

aptでインストールしたもの

#!/usr/bin/python
# GENERATED BY DEBIAN

import sys

# Run the main entry point, similarly to how setuptools does it, but because
# we didn't install the actual entry point from setup.py, don't use the
# pkg_resources API.
from pip import main
if __name__ == '__main__':
    sys.exit(main())

どちらも pip.main をそのまま実行していることがわかる。またaptはpipのスクリプトをそのまま使うのではなく、ディストリビューション向けにカスタマイズされている。
次に pip install pip で自動的にインストールされる最新版10.0.0のpipスクリプトの内容を見てみると、pip._internal.main を実行していることがわかる。

#!/usr/bin/python

# -*- coding: utf-8 -*-
import re
import sys

from pip._internal import main

if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
    sys.exit(main())

実際にpythonのライブラリが配置されているディレクトリ構造を確認してみると、明らかに構成が変わっていることがわかる。

9.0.1

~$ ls /usr/lib/python2.7/dist-packages/pip
commands  models      req    vcs      basecommand.py   baseparser.py   cmdoptions.py   download.py   exceptions.py   index.py   __init__.py   locations.py   __main__.py   pep425tags.py   status_codes.py   wheel.py
compat    operations  utils  _vendor  basecommand.pyc  baseparser.pyc  cmdoptions.pyc  download.pyc  exceptions.pyc  index.pyc  __init__.pyc  locations.pyc  __main__.pyc  pep425tags.pyc  status_codes.pyc  wheel.pyc

10.0.0

~$ ls .local/lib/python2.7/site-packages/pip
_internal  _vendor  __init__.py  __init__.pyc  __main__.py  __main__.pyc

これに加えてpythonのライブラリが参照されるパスの順序は、$HOME/.local/lib/python2.7/site-packages の方が /usr/lib/python2.7/dist-packages よりも優先度が高い。

~$ python -c 'import sys; print sys.path'
['', '/usr/lib/python2.7', '/usr/lib/python2.7/plat-x86_64-linux-gnu', '/usr/lib/python2.7/lib-tk', '/usr/lib/python2.7/lib-old', '/usr/lib/python2.7/lib-dynload', '/home/icchy/.local/lib/python2.7/site-packages', '/usr/local/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages/gtk-2.0']

そのため、import pip で参照されるpipは $HOME/.local/lib/python2.7/site-packages/pip (10.0.0) を指している。しかしaptでpipがインストールされている場合、pipコマンドで実行されるスクリプトは先に示した通り pip.main であり、これは10.0.0においては存在しない。
つまり最初の ImportError はpip9を想定しているスクリプトがpip10のライブラリを使おうとしたことによって発生したものである。

この問題についてのissueは上がっているのだが、メンテナーの考えとしては 「_internal に全ての機能を移動させたのにはそれなりの理由があって、pip.main はpip10では絶対にサポートしない」というものらしい。

github.com

おそらくディストリビューションのパッケージ更新によってpipスクリプトがアップデートされるのを待つしかないと思う。もちろん先に自分でpipスクリプトに手を加えて、pip._internal を使うようにするのはアリだとは思う。

pip installが動かない原因

まずDebian9 (stretch), Ubuntu 17.10 (artful) 以降の python-pip パッケージは 9.0.1-2 である。

python-pip 9.0.1-2からパッケージのsourceを取得して展開してみると、debian/patches/set_user_default.patch という名前のパッチが存在し、パッチの説明には以下のような記述がある。

When running as a normal user in a non-virtual environment, default to
--user and --ignore-installed.  When inside virtual environments or when
running as root, keep the default behavior.

つまりvirtualenv環境下でない場合は、デフォルトで --user フラグ ($HOME/.local/ 以下にインストールするオプション) を追加する。これにより、このバージョンの python-pip を使用していると pip install がほとんど成功するようになるため1 、sudoをつけずともインストールされるのが当たり前という感覚になってしまう。

一方pip10ではinstall時にverbosity levelを2以上 (-vv) にしないとエラーメッセージが見えなくなった。これはバグと認識されていて、どうやら返り値のチェックを EACCES ではなく EPERMにしてしまったことが原因らしい。

github.com

そのため、aptの python-pip 9.0.1-2 ユーザーがpip10を使うと pip install はいつも通り --user で実行されたものだと勘違いしてしまい、エラーメッセージも特に出ないのでパッケージがインストールされたように見えてしまうわけである。もちろんよくメッセージを見ると Successfully installed [package名] というメッセージが最後に出ていないので、インストールが正常に終わっていないことがわかるのだがまあなかなか気づきにくいと思う。

解決策

これらに対していくつかの解決策を考えた。

pip9を使い続ける

システムのpipに手を加えるのは少々危険が伴う。もし既存のパッケージに一切影響を与えたくないならば、pip9をそのまま使い続けるのが一つの手である。
set_user_default.patch が当たってるパッケージを使っている場合、もし pip install pip を実行してしまうと $HOME/.local/lib にpip10.0.0がインストールされてしまうため、例のバージョン違いによってpipが起動しなくなる。この場合は pip install pip==9.0.1 などを実行してインポートされるpipパッケージのバージョンを揃えておくと良い。

pip10に乗り換える

aptの python-pip を消して、/usr/local/bin/pip にpipをインストールする。pip10のバグによりインストール失敗のメッセージが見えなくなってしまうが、alias pip='pip -vv' とでもしておくか、普段から気をつければまあ問題はない。
python-pip で入れたpipでインストールするのはなるべく避けた方が良くて、公式が推奨しているget-pip.pyを使うのがよい。先ほど述べたようにインストール失敗のメッセージが出ない上に、本来pipはデフォルトのインストール先に /usr/local/ 以下を使うので、システムにインストールする場合はsudoをつけるのを忘れないようにする。もちろん -vv を使えばエラーは見える。

~$ python get-pip.py -vv
Created temporary directory: /tmp/icchy/pip-ephem-wheel-cache-nCRTal
Created temporary directory: /tmp/icchy/pip-install-n4gtqZ
Collecting pip
...
Installing collected packages: pip


Traceback (most recent call last):
  File "/tmp/icchy/tmp_NGCSM/pip.zip/pip/_internal/commands/install.py", line 335, in run
    use_user_site=options.use_user_site,
  File "/tmp/icchy/tmp_NGCSM/pip.zip/pip/_internal/req/__init__.py", line 49, in install_given_reqs
    **kwargs
  File "/tmp/icchy/tmp_NGCSM/pip.zip/pip/_internal/req/req_install.py", line 748, in install
    use_user_site=use_user_site, pycompile=pycompile,
  File "/tmp/icchy/tmp_NGCSM/pip.zip/pip/_internal/req/req_install.py", line 961, in move_wheel_files
    warn_script_location=warn_script_location,
  File "/tmp/icchy/tmp_NGCSM/pip.zip/pip/_internal/wheel.py", line 314, in move_wheel_files
    clobber(source, lib_dir, True)
  File "/tmp/icchy/tmp_NGCSM/pip.zip/pip/_internal/wheel.py", line 285, in clobber
    ensure_dir(destdir)
  File "/tmp/icchy/tmp_NGCSM/pip.zip/pip/_internal/utils/misc.py", line 86, in ensure_dir
    os.makedirs(path)
  File "/usr/lib/python2.7/os.py", line 157, in makedirs
    mkdir(name, mode)
OSError: [Errno 13] Permission denied: '/usr/local/lib/python2.7/dist-packages/pip-10.0.0.dist-info'
Cleaning up...

システムのpipに変更を加えずにpip10を使う

pipコマンドで起動するのがそもそもの原因なので、python -m pip でpipを起動すればなんら問題はない。

alias pip='python -m pip'

この場合システムのpipはどうなるんだと思うかもしれないが、$HOME/.local/lib をパッケージのパスに追加しているのでrootの場合は /usr/lib/python2.7/dist-packages/pip が参照される。つまり通常ユーザーはpip10を使い、システムはpip9を使うという奇妙な状態になる。もちろん各パッケージ自体はpythonのバージョンにしか依存しないので、pipのバージョンによって異なるパッケージが入ることはまずない。

まとめ

この問題の根本的な原因はpip10でディレクトリ構成の変更が行われたことにあった。変更にはそれなりの理由があって、pip側で解決策が打たれることはおそらくない。ディストリビューションのアップデートを待つか、自分で対応するしかなさそうというのが現状である。
解決策としてはこちらに示した3通りの方法が考えられる。他にも良い解決策があれば是非教えてほしい。

余談

pipのドキュメントは https://pip.pypa.io/en/stable/ で見られる。 pip 9.0.1のドキュメントを見るためにstableの部分を差し替えて https://pip.pypa.io/en/9.0.1/ にアクセスしたのだが、なんと403になっていた。9.0.3や10.0.0も同様なので、おそらくstableにしかアクセスできないようにして、そのシンボリックリンクとかを最新版に向けてるとかそんなところだと思う。


  1. 通常 --user をつけない場合は/usr/local/以下にインストールしようとするため、root権限がないと失敗する。

近況1

案の定日記を書くのが面倒になったので毎日の更新をやめた。

いろいろやっている。取り組むプロジェクトの候補を一つ一つ吟味しながら軽く論文を読んだり、OSSのツールがあった場合は適当に試したりしている。
当初はfuzzingなどの自動解析系をやりたいと思っていたが、ここにきて非常に興味深いプロジェクトが増えたので揺れてきた。

アメリカでの生活にはだいぶ慣れた。残るタスクは各種の住所変更と社会保障番号 (SSN) の取得である。SSNがないと税金が払えなくて、給料をもらえなくなってしまうので餓死する。
新しい宿は、インターネットが突然数分死ぬことがあるという点を除けばかなり快適である。インターネットのために研究室へ移動していると言っても過言ではない。今持ってるSIMはなぜかテザリングが使えない。

所持金がそろそろやばいことになりつつあるので外食や余計な買い物を控えている。と思ったら今日は$13くらいするラーメンを食べてUberで帰った。計画性がない。まあUberが紐づいている口座は日本のやつなので直接の影響はないのだが。
なぜUberを使ったかというと、研究室の人に「夜バスに乗るのは危ない」と言われたからである。聞くところによると、ルームメイトが夜バスに乗って強盗に遭い、持ち物全てを奪われたらしい。流石にPCを取られると終わりなのでかなり警戒している。

オンラインでいくつかのCTFにTokyoWesternsとして参加した。HITB-XCTFはなんと1位、Midnight Sun CTFは2位でどっちもfinalsがあるので時間と金と運があれば行くつもりである。最近CTFであまり活躍できていなかったのだが、出発前に参加した0ctfでは最後5分くらいでWebの解法を思いついて通せたし、先の2つのCTFでは結構問題解けたし幸先が良い。この調子でDEFCON予選とかも突破したい。

Linux環境を一新したらpipが壊れた。

この現象はすごい面白くて (こちらとしては全く面白くないが) 、debian9で python-pip をインストールするとシステム (/usr/bin/pip) にインストールされるが、以降のpipコマンドは $HOME/.local/ 以下にパッケージをインストールするため、もしpipの更新をかけると新しいpipのライブラリが $HOME/.local/lib に配置され、以降はそちらのライブラリが優先的に参照されるがpipコマンドは /usr/bin/pip のままなのでバージョン違いにより実行できないというものである。python -m pip を代わりに実行したり、$HOME/.local/bin$PATH に追加することによってこのエラーは回避できるのだが、なんと $HOME/.local/bin/pip は正しくパッケージをインストールできない。全くもってこの原因はつかめていないが、最新のdebian9でやると発現するので気になる各位は試してほしい。そしてもしよければ解決策を教えてほしい。

また気が向いたら更新する。

アメリカ day 8

day 2にも行ったハワイアンBBQに再び行ってみた。今回は昼の時間だったので"Lunch Special"なるものを注文してみた。

f:id:icchyr:20180411135658j:plain

割とボリュームがあると思いきやそうでもない。おそらくご飯が対して多くないせいだと思う。

大学に戻って研究テーマの候補をあれこれ思案していると、Ph.Dの学生がやってきてあるテーマについて熱烈なアプローチを受けた。かなり面白そうなのだが、結構実装が重いタイプのやつでなかなかリスキーである。システムプログラミングについては現在修士で取り組んでいるテーマでそこそこしんどい思いをしているのでできれば避けたい。

ついに定住先を見つけたので荷物をまとめて移動した。
大学からちょっと遠くなるものの、スーパーや薬局が近くにあるのでかなり便利っぽい。

前のAirbnbで借りていた家には直接外へ通じる鍵付きドアと家の中へ通じるドアがあって、前者は歪んでいるせいか下に2, 3cmほどの隙間があって、そこからよく虫が入って来て悩まされていた。もちろんAirbnb用にわざわざprivate entryを設けたのかもしれない。

せっかく引っ越したし近くにスーパーもあったので、早速自炊をしてみようということでチャーハンを作った。アメリカの米はインディカ米 (細長いやつ) が主流で、鍋を使って炊くのが一般的である。もちろん炊飯器を使う人もいるらしいがここには炊飯器は無いとのことで、鍋で20分ほど茹でることに。やはり火力も圧力も足りないので、最初に米が炊けたときは芯が残っていた。
ところがこれでチャーハンを作ってみるとめちゃくちゃ綺麗な (いわゆるパラパラ) チャーハンが作れるし、再度火を通すので芯は残ってないしでかなりうまくいった。ジャポニカ米はどうしても粘り気が出てきてしまうので、卵で米をコーティングしていい感じにパラパラ感を出すということをよくやるのだが、その必要が全くなかった。
生まれて初めて綺麗なチャーハンができたのでかなり満足している。

f:id:icchyr:20180411141522j:plain

自由に調理器具や食材を使わせてもらえるので、食費がかなり節約できそうだしこれからも積極的に自炊をやっていくぞという気持ちになった。

アメリカ day 6, 7

day6

アリゾナには日本人が運営している日本食材専門店があるらしくて、一度行ってみることにした。

fujiyamarket.com

割と品揃えは豊富で、特に調味料の類はかなり揃っていたと思う。
値段はやはり輸入品のためかなり割高である。ものにもよるがだいたい日本での価格の2倍程度だろうか。

外に出たついでに朝飯兼昼飯兼夕飯を食べた。Thai Basilという店があって、その名の通りタイ料理が食べられる。
去年の末にICPCでタイに行ったときにもいろいろ食べたが、アメリカで出るタイ料理はどんなもんだろうとPad Thaiを頼んでみた。

f:id:icchyr:20180410093216j:plain

写真だとわかりにくいかも知れないが、やはりサイズが大きい。直径30cmというのは標準規格なのか? 味はタイで食べたのと似たような感じで、タイ語っぽいものが聞こえてきたし多分本場のタイ人が調理しているんだと思う。
一応ウェイターがいるレストランなのでそれなりに値が張った。

day7

まず大学に行ってzardusと研究テーマについてのミーティングをしようとコンタクトを取ったら、 ちょうどShellphishのFishが来ていて、angrのコアなcontributor2人と昼食を取るという非常に貴重な体験をした。
ちなみに食べたのは醤油ラーメン:

f:id:icchyr:20180410094845j:plain

味は割と普通で、強いていうなら麺が柔らかかったが普通に美味しかった。

そのあとは各種手続きの為に大学をあちこち歩き回って疲れた。ある程度英語は聞き取れるが、発音がめちゃくちゃ早いとつい聞き返してしまうことが何度も発生してつらい気持ちになる。

次の住居が決まったので少し早く切り上げて明日にも移動しようと思っている。これで少しはAirBnbの宿代が浮いてほしい。

アメリカ day 5

またしても4時に目が覚めた。しんどい
偶然同じ学科に日本人でPh.Dの人がいて、詳しく話を聞かせてもらった。とにかく水分補給をこまめにした方が良いと言われた。聞くところによると夏のやばい日は50℃くらいまで上がるらしい。地獄か?

ハンバーガーをお昼ご飯に選んでしまった。 f:id:icchyr:20180408162135j:plain ここに来てからハンバーガーとピザ以外のものを口にしていない気がするのでそろそろなんとかしたい。あとポテトの塩がめちゃくちゃ多くてしょっぱい。
こんな食事をしているのと不自然な生活リズムが合わさって、一日一食というという日が数日発生している。健康にも良くないし早く自炊をベースとした生活に切り替えたい。

先にあげたPh.Dの方のおかげで家問題が解決しそうである。家探しは基本的に自分で契約するか又貸し (subleaseという) する人を探すしかなくて、前者は十分な収入が求められたり契約期間が1年からと決まっていたりして海外から来たばかりの人には難しい。後者も同様にあまり素性の知れない、安定した身分を持っていない人には基本貸さないらしく、知り合い経由などで頼むのが無難とのこと。
そのPh.Dの方の知り合いにちょうど空きがある家を持ってる人がいたので、家主にコンタクトを取ったところ幸運にもすぐにOKが出た。とても感謝している。

アメリカ day 4

夜の10時過ぎに目が覚めた結果、結局朝方まで眠れなくて昼過ぎに起きた。
入国してからずっと休日がなかったので、昨日もらった研究テーマ一覧を眺めながら1日休むことにした。

そろそろ自炊がしたいけど安定した宿が得られるまでは我慢かなあ、外食で所持金が尽きないことを願う。

アメリカ day 3

朝4時くらいに目が覚めた。少しずつ体が慣れていってるのがわかる。
流石に朝食をとりたいので、9時過ぎくらいに宿を出て大学周辺を散策していた。これは歩いてみてわかったことだが、土地が異様に広いので複数の場所を行ったり来たりするだけでかなり疲れる。最終的にChick-fil-Aというハンバーガーショップみたいなところに入ることにした。

f:id:icchyr:20180406160950j:plain

写真にあるハンバーガーとフライドポテトに加え、ドリンクがついて$6.86である。日本円にすると約737円 (4/5時点) で、決して安くはない。
朝食を済ませた後に銀行口座の開設をした。現時点ではSSNが無かったが、パスポートのみで手続きを済ませることができた。たまたま研究室がある建物のすぐ隣にBoA (Bank of America) があって、開設手続きはその場で全て済み、一時的に使えるキャッシュカード (記名無し) を受け取った。やはりカード決済が主流だからかデビットカードの機能が標準でついており、とりあえず有り金をすべて入れるとすぐ店で使えた。レジで現金 (とくに硬貨) を数えるのに毎回手間取っていたのでこういう手軽さは非常にありがたい。
その後は研究室のミーティングに参加し、軽い自己紹介とこれから取り組むテーマの一覧を見させてもらった。正直なところ発音が早過ぎて7, 8割程度しか聞き取れなかったが、いずれのテーマもセキュリティに関連した面白いアイデアで、アカデミックというよりかはハッカー的な、いわゆる純粋に面白さを追求しているものが多いように感じた。

ミーティングのあとは研究室の人に連れられてピザ屋に入った。このピザ屋ではなんとオーダーメイドのピザを作ることができる。ソースや具材を適当に選んで、最終的にできたのがこちら

f:id:icchyr:20180406163216j:plain

なんとこれは一人分である。直径が30cmくらいで、当然全て食べきれなかったので箱に入れて持ち帰った。ちなみに午後5時まで有効なクーポンを使うとこれにフリードリンクがついて$5、流石にこれは結構安いと思う。(もっとも、今回は研究室の人にご馳走になったわけだが)
その後はASUのCTFチームであるpwndevilsのミーティングに出席した。つい先日の0CTFの問題を復習していたので、ezDoorの解法をシェアしたところ好評だった。
いろいろがあってだいぶ疲れてたため、19時くらいに宿に着いたらすぐ眠ってしまった。そして案の定22時過ぎに目が覚めて今に至る。生活リズムの修正にはもうしばらくかかりそうである。