Quantcast
Channel: Vimタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 5608

ALE(on NeoVim)でPythonコードを楽に整形する

$
0
0

tl;dr;

PythonのLinterであるflake8はpep8に従っていろいろと教えてくれますが、
マニュアルで行うとなかなかに面倒な修正案を提示します。
近頃Golangの自動フォーマッタに慣れてしまった自分としては、
もう勝手にやってくれよ、とちょいちょい思ってました。

非同期にLinterを実行するVimプラグインとして有名なALEには、
エラー箇所を自動で修正するALEFixコマンドがあり、前々からやってみようかなと思っていたので、
この機会にALEでPythonコードのLintと自動整形をやらせてみたいと思います。

前提

NeoVim

私は素のVimではなくNeoVim使いなのでこっちを使った方法になります。
素のVimとはPythonのパス解決の仕方が異なるのでご注意ください。

  • NVIM v0.3.1

あとALEを使うのでALEのインストールはもちろん必要です。公式は丁寧に書いてあるのでわかりやすいです。

pyenv

ALEから呼び出しするflake8などのコマンドはpyenvにてインストールしたPythonのものを使用します。
これはNeoVimから呼び出すPythonのパス解決を固定するためです。

  • pyenv 1.2.7-1-g71902168

pyenvにてインストールしたPythonのパスを固定する手段としては、
deoplete-jediのwikiが非常に参考になるため参照してみてください。

準備

まず準備としてNeovimが参照するPythonのパスが固定されている必要があります。
なぜならばNeoVim起動時にどのPythonを参照するかによって都度都度必要なツールをインストールするのは面倒だからです。

pyenvでNeoVim参照用のPython仮想環境を作成する

開発者であればPythonはvirtualenvやpipenv,pyenvなどにより複数のPythonバージョンを持っていることが多いと思います。
Pythonのパスが固定されていなければ、ALEから呼び出しするflake8等のPythonツール群は、
実行環境によって様々なPythonパスを参照してしまい、参照したパスにツールがインストールされていない場合、
パス解決ができずALEから実行できない恐れがあるからです。

globalのpyenv使ってるときは問題ないのに、virtualenvをロードした後にALEでflake8実行できなかったことないですか?
あったら原因はパス解決のせいだと思います。

手順は以下のツールが既にインストール済みである前提で話を進めます

  1. NeoVim参照用のPythonを作る

    # pyenvに最新のpython3を入れる
    pyenv install 3.6.5
    # pyenv-virtualenvでneovim用の仮想環境として定義
    pyenv virtualenv 3.6.5 neovim3
    
  2. pyenvのパスは環境変数に入れとく

    export PYENV_PATH=$HOME/.pyenv
    
  3. init.vimにPythonのパスを入れる

    letg:python3_host_prog= $PYENV_PATH .'/versions/neovim3/bin/python'
    • ちなみに自分はanyenvとpyenvが端末によって入ったり入ってなかったりするので以下のように書いてます

      if isdirectory(expand($PYENV_PATH))letg:python3_host_prog= $PYENV_PATH .'/versions/neovim3/bin/python'endifif isdirectory(expand($ANYENV_PATH))letg:python3_host_prog= $ANYENV_PATH .'/envs/pyenv/versions/neovim3/bin/python'endif

ツールのインストール

なお今回使用するツールは以下の通りです。

ツール名用途
flake8いわずもがなPythonのLinter
flake8-import-orderflake8でimport順序をチェックする拡張
autopep8pep8の規約に沿って整形するフォーマッタ
black改行を整形するフォーマッタ
isortimport順序を整形するフォーマッタ

下記手順で、pyenvに必要ツールぶち込みます

# 上記で作った環境をロード
pyenv shell neovim3
# 仮想環境に必要ツールをインストール
pip install-U flake8 flake8-import-order autopep8 black isort
# アンロード
pyenv shell --unset

ALEの設定

なかなか面倒ですがやっとALEの設定ができます。
大きく分けてやりたいことは以下の3つです。

  1. flake8をLinterとして登録
  2. 各ツールをFixerとして登録
  3. 各ツールの実行オプションを変更してPythonパスを固定
" flake8をLinterとして登録letg:ale_linters={    \'python':['flake8'],    \}" 各ツールをFixerとして登録letg:ale_fixers={    \'python':['autopep8','black','isort'],    \}" 各ツールの実行オプションを変更してPythonパスを固定letg:ale_python_flake8_executable=g:python3_host_progletg:ale_python_flake8_options='-m flake8'letg:ale_python_autopep8_executable=g:python3_host_progletg:ale_python_autopep8_options='-m autopep8'letg:ale_python_isort_executable=g:python3_host_progletg:ale_python_isort_options='-m isort'letg:ale_python_black_executable=g:python3_host_progletg:ale_python_black_options='-m black'" ついでにFixを実行するマッピングしとく
nmap <silent><Leader>x<Plug>(ale_fix)" ファイル保存時に自動的にFixするオプションもあるのでお好みでletg:ale_fix_on_save=1

補足

わかりづらいので解説しときますと各ツールの実行オプションを変更してPythonパスを固定のところは

オプション説明
g:ale_python_*_executableFixer実行時のPythonのパスを指定
g:ale_python_*_optionsg:ale_python_*_executableのパスで実行した場合のPythonのオプションを指定

となっているご様子です。英語弱者なので解釈違ったら申し訳ございません。
特にg:ale_python_*_optionsは自信ない...

ちなみにPythonの-mオプションは-mで指定したPythonモジュールを実行するフラグですね。

あと、別にg:python3_host_progを使わなくてもpyenvで作ったpyenv-virtualenv環境を、
g:ale_python_*_executableに入れてあげてもよかったのですが、
こっちのほうが後でpyenvの環境つくり直したときとかに便利かなと思ってあえてそう書いてます。
素のVim使いの方々はg:ale_python_*_executableに直接書いてもいいかも。

実はyapfってフォーマッタがまだあって、
これが賢いらしいので使ってみたかったのですが、なんかALE側がちゃんと整備できてなさそう。
g:ale_python_*_executable以外のyapf用のオプション何故かないんですよね。
そのうちPR送りたい。

実行する

autopep8のサンプルをはっつけるので試してみましょう。

importmath,sys;defexample1():####This is a long comment. This should be wrapped to fit within 72 characters.some_tuple=(1,2,3,'a');some_variable={'long':'Long code lines should be wrapped within 79 characters.','other':[math.pi,100,200,300,9876543210,'This is a long string that goes on'],'more':{'inner':'This whole logical line should be wrapped.',some_tuple:[1,20,300,40000,500000000,60000000000000000]}}return(some_tuple,some_variable)defexample2():return{'has_key() is deprecated':True}.has_key({'f':2}.has_key(''));classExample3(object):def__init__(self,bar):#Comments should have a space after the hash.ifbar:bar+=1;bar=bar*bar;returnbarelse:some_string="""
                       Indentation in multiline strings should not be touched.
Only actual code should be reindented.
"""return(sys.path,some_string)

多分以下のように整形されるはずです。やったね。

importmathimportsysdefexample1():# This is a long comment. This should be wrapped to fit within 72 characters.some_tuple=(1,2,3,"a")some_variable={"long":"Long code lines should be wrapped within 79 characters.","other":[math.pi,100,200,300,9876543210,"This is a long string that goes on",],"more":{"inner":"This whole logical line should be wrapped.",some_tuple:[1,20,300,40000,500000000,60000000000000000],},}return(some_tuple,some_variable)defexample2():return{"has_key() is deprecated":True}.has_key({"f":2}.has_key(""))classExample3(object):def__init__(self,bar):# Comments should have a space after the hash.ifbar:bar+=1bar=bar*barreturnbarelse:some_string="""
                       Indentation in multiline strings should not be touched.
Only actual code should be reindented.
"""return(sys.path,some_string)

Viewing all articles
Browse latest Browse all 5608

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>