え?君せっかく Python のバージョン管理に pyenv 使ってるのに Vim の補完はシステムライブラリ参照してるの?
どうも、ご無沙汰してます有末です。 Pythonistaならpyenvだよねーってことで当初からバリバリ使わせていただいているのですが、最近djangoのプロジェクトを書く際に困ったのでまとめておきます。 具体的にはpyenvでPytho 3をインストールし、pyenv-virtualenvを用いて仮想環境を構築し、その仮想環境に django をインストールしただけでは jedi-vim の補完が効かないという問題です。 いくつかの要因が複合して複雑に成っていたので、ひとつずつメモしていきます。
忙しい人のための簡易書
普段からpyenvとpyenv-virtualenvを使用していてjedi-vimでdjangoの補完が効かなくて困っている。
とにかく補完を効かせたい。
戯言なんてどうでもいいという人は下記を~/.vimrc
に記載してください。
" ~/.pyenv/shimsを$PATHに追加 " jedi-vim や vim-pyenc のロードよりも先に行う必要がある、はず。 let $PATH = "~/.pyenv/shims:".$PATH " ... neobundle.vim 初期化等 " DJANGO_SETTINGS_MODULE を自動設定 NeoBundleLazy "lambdalisue/vim-django-support", { \ "autoload": { \ "filetypes": ["python", "python3", "djangohtml"] \ }} " 補完用に jedi-vim を追加 NeoBundle "davidhalter/jedi-vim" " pyenv 処理用に vim-pyenv を追加 " Note: depends が指定されているため jedi-vim より後にロードされる(ことを期待) NeoBundleLazy "lambdalisue/vim-pyenv", { \ "depends": ['davidhalter/jedi-vim'], \ "autoload": { \ "filetypes": ["python", "python3", "djangohtml"] \ }} " ... neobundle.vim 終了処理等
これにより現在使用している(pyenv方式)の virtualenv を自動的にVim上で適用します。
手動による virtualenv の適用・解除はそれぞれ :PyenvActivate
と :PyenvDeactivate
で行えます。
また DJANGO_SETTINGS_MODULE
が自動的に設定されるようになったので、jedi-vimによるdjangoの補完が効きます。
なおneobundle.vimを使っていることを前提で書いていますが、以下の二点に気をつけていれば他の方法でも構いません。
- jedi-vimやvim-pyenvが読み込まれる前に
~/.pyenv/shims
(もしくはそれに準ずるディレクトリ)が$PATH
に加わっている - vim-pyenvはjedi-vimよりも後にロードされる
普通のvirtualenvを使用している場合はVimを最強のPython開発環境にする2に習ってvim-pyenvの代わりにvim-virtualenvを使用してください。
Python2もしくはPython3のみを使用する場合
pyenvとpyenv-virtualenvは使っているがPython2かPython3しか使わないという方向けの方法です。 ただ、何度も同じ事を説明するのも面倒なので、Python2とPython3どちらも使用する人も同じ設定を施してください。
Vimでインストールしたライブラリなど、一部の補完が効かない場合というのは通常 sys.path
の設定が適切でないために発生します。
Vimは内部で独自にPythonの実行環境を持っているため、Terminal上でいくら pyenv virtualenv Hello; pyenv local Hello
などとしても無駄足となります。
そこでVimの内部Pythonの sys.path
を適切に書き換えてやるために手前味噌ですが vim-pyenv というプラグインを利用します。
vim-pyenv は pyenv local Hello
などのコマンドにより適用された変更を自動的に読み取り、Vim内部のPythonに適用します。
これにより Hello
にインストールされたライブラリも検索パスに含まれるようになるため jedi-vim での補完が可能となります。
また django のケースですが、補完が効かない最も大きな理由は DJANGO_SETTINGS_MODULE
が適切に設定されていないことです。
django の一部のモジュール(例 django.db
)は読み込む際に DJANGO_SETTINGS_MODULE
の値を使用して自身の設定コードを呼び出します。
したがって DJANGO_SETTINGS_MODULE
が設定されていないか不適切な場合は jedi-vim のような「モジュールをロードして補完リストを組み上げるタイプ」のプラグインでは、適切に補完リストが組めず補完ができなくなります。
適切に DJANGO_SETTINGS_MODULE
を設定するのは骨が折れるので、これも手前味噌ですが vim-django-support というプラグインを利用します。
ご想像通り、このプラグインは状況により適切に DJANGO_SETTINGS_MODULE
を設定するためのものです。
最後に pyenv を使用する際の注意です。
pyenv を使用した場合Pythonの実行パスは ~/.pyenv/shims/python
となり、これは pyenv init
により PATH
が適切に設定されるために実現します。
しかし Vim は独自に PATH
を持っているため、このままでは pyenv のPythonを参照することができません。
したがって下記のように PATH
を書き換えてやる必要があります。
let PATH = expand("~/.pyenv/shims") . ":" . $PATH
ただし、このままだと ~/.vimrc
のリロードなどにより PATH
が凄いことになってしまうので下記のような関数を作って登録するようにしましょう。
" PATHの自動更新関数 " | 指定された path が $PATH に存在せず、ディレクトリとして存在している場合 " | のみ $PATH に加える function! IncludePath(path) " define delimiter depends on platform if has('win16') || has('win32') || has('win64') let delimiter = ";" else let delimiter = ":" endif let pathlist = split($PATH, delimiter) if isdirectory(a:path) && index(pathlist, a:path) == -1 let $PATH=a:path.delimiter.$PATH endif endfunction " ~/.pyenv/shims を $PATH に追加する " これを行わないとpythonが正しく検索されない IncludePath(expand("~/.pyenv/shims"))
この設定はPythonを扱うプラグインが読み込まれる前に行う必要があります。
したがって ~/.vimrc
の先頭あたりに書いておくとよいでしょう。
上記を踏まえた ~/.vimrc
は下記のようになります。なお neobundle.vim を使用することを前提としていますが他のパッケージマネージャを用いても構いません。
" ... 省略 " PATHの自動更新関数 " | 指定された path が $PATH に存在せず、ディレクトリとして存在している場合 " | のみ $PATH に加える function! IncludePath(path) " define delimiter depends on platform if has('win16') || has('win32') || has('win64') let delimiter = ";" else let delimiter = ":" endif let pathlist = split($PATH, delimiter) if isdirectory(a:path) && index(pathlist, a:path) == -1 let $PATH=a:path.delimiter.$PATH endif endfunction " ~/.pyenv/shims を $PATH に追加する " これを行わないとpythonが正しく検索されない IncludePath(expand("~/.pyenv/shims")) " ... neobundle.vim 初期化等 " DJANGO_SETTINGS_MODULE を自動設定 NeoBundleLazy "lambdalisue/vim-django-support", { \ "autoload": { \ "filetypes": ["python", "python3", "djangohtml"] \ }} " 補完用に jedi-vim を追加 NeoBundleLazy "davidhalter/jedi-vim", { \ "autoload": { \ "filetypes": ["python", "python3", "djangohtml"] \ }} " pyenv 処理用に vim-pyenv を追加 " Note: depends が指定されているため jedi-vim より後にロードされる NeoBundleLazy "lambdalisue/vim-pyenv", { \ "depends": ['davidhalter/jedi-vim'], \ "autoload": { \ "filetypes": ["python", "python3", "djangohtml"] \ }} " ... neobundle.vim 終了処理等
vim-pyenv はデフォルトで現在選択されている virtualenv を適用します。
virtualenv の切り替えは :PyenvActivate
と :PyenvDeactivate
で行えます。
このあたりの詳しい説明は :help vim-pyenv
をご覧ください。
Python2とPython3どちらも使用する場合
何度も同じ事を説明するのも面倒なので、前章を読んでいないかたは、まず前章と同じ設定を施してください。
pyenv を使う一番のメリットはPythonのバージョンを簡単に切り替えられることだと思います。
このメリットを最大限にVimで活かすためには、Vimが +python/+python3
でコンパイルされている必要があります。
この確認は vim --version
を使って以下のように行えます。
$ vim --version VIM - Vi IMproved 7.4 (2013 Aug 10, compiled May 20 2014 18:20:07) 適用済パッチ: 1-295 Compiled by alisue@alisue-labdesk 通常 版 with GTK2-GNOME GUI. 機能の一覧 有効(+)/無効(-) ...略... +cryptv +linebreak +python/dyn +viminfo +cscope +lispindent +python3/dyn +vreplace ...略...
実行結果の中に +python/dyn
と +python3/dyn
という文字がありますが、これが +python/+python3
の状態です。
どちらかの頭に -
がついている場合は、残念ながらPython2かPython3のどちらかが正しく機能しません。
また確認が面倒くさいので行なっていませんが /dyn
という文字がついていない場合はどちらかが使えないかもしれません(要出典)。
どちらも使えるかどうか確認するには、Vimを起動して下記二つのコマンドで正しい値が帰ってくることを確認してください。
:python print(sys.version) :python3 print(sys.version)
Macユーザーの方はMac-Vim-Kaoriyaのバイナリが+python/+python3
なようなのでそれを利用すれば良いようです(未確認)。
Linuxユーザーの方で +python/+python3
では無かった方は下記方法にしたがって pyenv でインストールした Python2, Python3 を参照するVimをコンパイルしなおしてください。
Vimを+python/+python3でコンパイル
通常VimはPythoと静的リンクしますが、Python 2,3 を共存させたい場合は動的リンクにする必要があります。
詳しいことは割愛しますが、動的リンクを可能にするために--enable-shared
オプションを指定してPythonをコンパイルるする必要があります。
pyenv でこのオプションを指定して Python2, 3 をインストールします。
下記に従ってください。
なお、正しく --enable-shared
でインストールされたかどうかは libpython2.7.so.1.0
や libpython3.4m.so.1.0
が $HOME/.pyenv/versions/XXXX/lib/
の中に生成されているかどうかで確かめることができます。
$ CONFIGURE_OPTS="--enable-shared" pyenv install 2.7.6 $ CONFIGURE_OPTS="--enable-shared" pyenv install 3.4.0
次にこの二つの Python をリンクさせたVimをコンパイルします。 まずは正しく Python 2, 3 を探せるように下記のように同時に二つのバージョンを指定します
$ pyenv local --unset $ pyenv shell --unset $ pyenv global 2.7.6 3.4.0 $ pyenv versions system * 2.7.6 (set by /home/XXXX/.pyenv/version) * 3.4.0 (set by /home/XXXX/.pyenv/version) XXXXX $ python --version Python 2.7.6 $ python3 --version Python 3.4.0
ここまでで利用する Python のインストールが終わったのでコンパイルする Vim のソースコードを Marcurial でレポジトリから落としてきます。 下記に従ってください
$ hg clone https://code.google.com/p/vim/
$ cd vim
ここで適切なオプションを付加して ./configure {options}
すれば Vim のコンパイルが行われますが、動的リンクであるため出来上がったバイナリファイルが pyenv でインストールした Python を見つけることができません。
LD_LIBRARY_PATH
などでリンク先を指定しても良いのですがバイナリ固有のライブラリ検索パスとして -rpath
オプションがあるので、これを用いて下記のようにライブラリ検索パスを指定します。
なお下記したオプションはすべて ./configure --help
に解説が書いてあるので必要に応じて追加・削除してください。
$ LDFLAGS="-Wl,-rpath=${HOME}/.pyenv/versions/2.7.6/lib:${HOME}/.pyenv/versions/3.4.0/lib" ./configure \ --enable-fail-if-missing \ --enable-luainterp \ --enable-perlinterp \ --enable-pythoninterp=dynamic \ --enable-python3interp=dynamic \ --enable-tclinterp \ --enable-rubyinterp=yes \ --enable-multibyte \ --enable-fontset \ --enable-gui=gnome2 \ --with-features=huge \ --with-luajit $ make
これで src/vim
にバイナリファイルが作成されるので srv/vim --version
を実行して +python/+python3
なバイナリが出来上がったかを確認してください。
また -rpath
が適切に設定されたかどうかは readelf -d srv/vim
で表示される Library rpath:
にて確認することができます。
最後に念の為Vimを起動し、下記コマンドで Python 2, 3 双方が実行できることを確認して sudo make install
してください。
:python print(sys.version) :python3 print(sys.version)
参考
- Can't figure out how to build a python that uses the .SO file #65
- Debian系のLinuxでPython 2.xと3.xが同時利用できない問題の原因と対策 #301
+python/+python3 でなければ行けない理由
とりあえず何故この様な面倒なことをする必要が合ったかについて簡単に説明します。
ご存知の通り Python には 2x 系列と 3x 系列が存在します。
両者は構文レベルでの互換性を捨てているため、Python2のコードをPython3で読む、またその逆も出来ません。
さて、ここで pyenv を使用すると Python2 と Python3 を簡単に切り替えることができます。
さらに言えば pyenv-virtualenv を使用するとベースが Python2 や Python3 の様々な仮想環境を構築することができます。
そのため、補完を効かせたいがために無理やり sys.path
や PYTHONPATH
を書き換えると Python3 で Python2 のライブラリを読み込むミスが発生し、嵐の如きエラーに見舞われることになります。
したがって、補完プラグインは読み込むライブラリがどちらに向けたライブラリなのかを把握し、適切に使用するPythonを選択する必要があります。
もうお分かりかとは思いますが、この切り替えを行うためには Vim が +python/+python3
でコンパイルされている必要があったので、面倒ですが上記のような手順をふみました。
ちなみに、この Python2/3 の切り替えに対応している補完プラグインは、僕が知る限りでは jedi-vim のみです。 ただし、このjedi-vimはユーザーが事前に設定したPython2/3を使用して補完を試みるだけなので、仮想環境を切り替えるだけでは先の問題が解決できません。 したがって vim-pyenv では選択された仮想環境に応じて適切に自身と jedi-vim が使用するPythonのバージョンを切り替える設計になっています。
vim-pyenv を使用して動的に仮想環境と使用Pythonを切り替える
vim-pyenv が +python/+python3
な Vim にインストールされると自動で Python のバージョン切り替え機能が有効化されます。
すべての処理がバックグラウンドで行われるので通常は意識する必要がありませんが、今回は確認も兼ねて一つづつステップを踏んでいきます。
まずは Python 2/3 それぞれをベースとした仮想環境 django2/3 を作成し、それぞれにdjangoをインストールします。
$ pyenv virtualenv 2.7.6 django2 $ pyenv virtualenv 3.4.0 django3 $ pyenv shell django2 $ pip install django $ pyenv shell django3 $ pip install django $ pyenv rehash
ここで Vim を起動すると vim-pyenv, jed-vim が使用する Python のバージョンは下記のように外部の Python のバージョンにあわせて初期化されます
Name | 起動直後 | jedi-vim 初期化 | vim-pyenv 初期化 | pyenv#activate() |
---|---|---|---|---|
Terminal | 3.4.0 | 3.4.0 | 3.4.0 | 3.4.0 |
jedi-vim | 2.7.6 | 2.7.6 | 3.4.0 | |
vim-pyenv | 2.7.6 | 3.4.0 |
したがって、仮に vim-pyenv が jedi-vim より先に読み込まれてしまうと、下記のようになってしまい正しく初期化が行えないことになります。
Name | 起動直後 | vim-pyenv 初期化 | pyenv#activate() | jedi-vim 初期化 |
---|---|---|---|---|
Terminal | 3.4.0 | 3.4.0 | 3.4.0 | 3.4.0 |
vim-pyenv | 2.7.6 | 3.4.0 | 3.4.0 | |
jedi-vim | 3.4.0 | 2.7.6 |
実際に正しいバージョンの Python が使用されているかどうかは下記のコマンドで調べることができます。
:Python print("jedi-vim", sys.path) :PyenvPython print("vim-pyenv", sys.path)
まぁ正しく設定されていたと仮定します。 次に仮想環境を django2(Python 2.7.4 ベース)に切り替えてみましょう。 下記コマンドを実行してください。
:PyenvActivate django2
これで自動的に jedi-vim, vim-pyenv が Python 2 を参照するようになります。
またライブラリの参照先も Python 3 向けのもの(~/.pyenv/versions/django3/lib/python3.4/site-packages
) から Python 2 向けのもの(~/.pyenv/versions/django2/lib/python2.7/site-packages
)に変更されます。
lightline に現在の仮想環境とPythonのバージョンを表示する
vim-pyenv はスクリーンショットのようなインディケータを提供しています。
文字列を返す関数なので lightline.vim だけでなくVim標準のstatuslineなどでも利用できます。
詳しく知りたい方は :help pyenv#statusline#component()
としてください。
とりあえず僕の lightline.vim の設定を下記します。
let g:lightline = { \ 'colorscheme': 'hybrid', \ 'active': { \ 'left': [ \ [ 'mode', 'paste' ], \ [ 'pyenv' ], " <= ここ \ [ 'fugitive', 'filename' ] \ ], \ 'right': [ \ [ 'syntastic', 'lineinfo' ], \ [ 'percent' ], \ [ 'fileformat', 'fileencoding', 'filetype' ] \ ] \ }, \ 'component_expand': { \ 'syntastic': 'SyntasticStatuslineFlag', \ }, \ 'component_type': { \ 'syntastic': 'error', \ }, \ 'component_function': { \ 'fugitive': 'MyFugitive', \ 'filename': 'MyFilename', \ 'fileformat': 'MyFileformat', \ 'filetype': 'MyFiletype', \ 'fileencoding': 'MyFileencoding', \ 'mode': 'MyMode', \ 'pyenv': 'pyenv#statusline#component', " <= ここ \ }, \ 'separator': { 'left': '⮀', 'right': '⮂' }, \ 'subseparator': { 'left': '⮁', 'right': '⮃' } \ }
やっぱり美しくないとね!