EmacsでのElpyをベースとしたPython開発環境

2020年12月2日 2023年12月11日

この記事はEmacs Advent Calendar 2020の2日目の記事です。

こんにちは。 この記事では、私のEmacsでのElpyをベースとしたPython開発環境を紹介します。 設定の仕方は十人十色ですが、こんな設定あるんだというようなことに繋がると嬉しいです。

Pythonの開発環境

まず、Python自体の開発環境は、pyenv、pipx、poetryを用いて構築します。 これらを用いることで、プロジェクトごとに開発環境を分離しています。

具体的な構築方法は、Python開発環境(pyenv+pipx+poetry)の構築(2020)に記載していますので、よろしければご参照ください。

Emacsでの開発環境

では、本題のEmacsでの開発環境です。 PythonをEmacsで快適に書くために、以下のEmacsパッケージを導入してます。

Emacsパッケージ概要
highlight-indent-guidesインデントの強調表示
rainbow-delimiters対応する括弧の強調表示
whitespace-mode空白の可視化
mwim行頭・行末移動の拡張
py-isortpythonファイルのimport順の整理
company-modeEmacsの入力補完用パッケージ
yasnippetテンプレートの挿入
Poetry.elpoetryのラッパー
Flycheck構文チェッカー
ElpyEmacsでのPython統合開発環境

では、具体的に各パッケージの説明と設定を紹介します。

なお、私はPackage管理のパッケージとして、leaf.elを使用していますので、今回は、leaf.elの記法で設定していきます。 個人的にはleaf.elは可読性が高く、設定もしやすいので、とてもありがたいです。

highlight-indent-guides

highlight-indent-guidesは、インデントの位置を強調表示するマイナーモードです。

どのように強調表示されるかは公式サイトのスクリーンショットをご覧ください。 特にPythonはindent位置が重要になりますので、重宝しています。

(leaf highlight-indent-guides
  :ensure t
  :blackout t
  :hook (((prog-mode-hook yaml-mode-hook) . highlight-indent-guides-mode))
  :custom (
           (highlight-indent-guides-method . 'character)
           (highlight-indent-guides-auto-enabled . t)
           (highlight-indent-guides-responsive . t)
           (highlight-indent-guides-character . ?\|)))

rainbow-delimiters

rainbow-delimitersは、対応する括弧を強調表示するマイナーモードです。 以下の設定を行うことで、対応する括弧を色分けして表示できますので、対応関係を視認しやすくなります。

(leaf rainbow-delimiters
  :ensure t
  :hook
  ((prog-mode-hook       . rainbow-delimiters-mode)))

white-space-mode

white-space-modeは、スペースやタブなどの空白を表示するマイナーモードです。また、不要なスペースやタブの削除も可能です。

設定は@itiut@github様のqiita記事を参考に以下のように設定しています。 C-c Wで不要なスペースやタブを明示的に除去するようにしています。

(leaf whitespace
  :ensure t
  :commands whitespace-mode
  :bind ("C-c W" . whitespace-cleanup)
  :custom ((whitespace-style . '(face
                                trailing
                                tabs
                                spaces
                                empty
                                space-mark
                                tab-mark))
           (whitespace-display-mappings . '((space-mark ?\u3000 [?\u25a1])
                                            (tab-mark ?\t [?\u00BB ?\t] [?\\ ?\t])))
           (whitespace-space-regexp . "\\(\u3000+\\)")
           (whitespace-global-modes . '(emacs-lisp-mode shell-script-mode sh-mode python-mode org-mode))
           (global-whitespace-mode . t))

  :config
  (set-face-attribute 'whitespace-trailing nil
                      :background "Black"
                      :foreground "DeepPink"
                      :underline t)
  (set-face-attribute 'whitespace-tab nil
                      :background "Black"
                      :foreground "LightSkyBlue"
                      :underline t)
  (set-face-attribute 'whitespace-space nil
                      :background "Black"
                      :foreground "GreenYellow"
                      :weight 'bold)
    (set-face-attribute 'whitespace-empty nil
                      :background "Black")
  )

mwim

mwimは、コードの先頭・末尾への移動と行頭・行末への移動を行うコマンドを提供します。 実際の動作は公式サイトのスクリーンショットをご参照ください。 私はC-aC-eに割り当てて、行頭・行末への移動を拡張しています。

(leaf mwim
  :ensure t
  :bind (("C-a" . mwim-beginning-of-code-or-line)
            ("C-e" . mwim-end-of-code-or-line)))

py-isort

py-isortは、外部のisortコマンドを利用して現在開いているバッファー内のimport順を整理するパッケージです。

isortコマンドはimportするライブラリの順序をPEP8で定義される以下のルールに従ってソートします。

import文 は次の順番でグループ化すべきです:

  1. 標準ライブラリ
  2. サードパーティに関連するもの
  3. ローカルな アプリケーション/ライブラリ に特有のもの

上のグループそれぞれの間には、1行空白を置くべきです。

py-isortは外部コマンドであるisortが必要となりますので、isortコマンドを別途インストールします。

isortコマンドはpipでインストールできますので、globalのpyhtonにインストールか、pipでインストールするコマンドの管理を参考にpipxでインストールしてください。おすすめは管理がしやすいpipxでのインストールです。

Emacsでは、以下の設定でpy-isortを有効化します。

(leaf py-isort :ensure t)

上記にbefore-save-hookpy-isort-before-saveフックする設定を追加することで、バッファの保存時に自動でソートするようにもできますが、私は自分でソートタイミングを指定したいので、設定せずにM-x py-isort-bufferで、明示的にソートしています。

company-mode

company-modeは、Emacsの入力補完用のパッケージです。 python以外でも色々な言語等で入力補完ができます。

私は以下の設定で、1文字入力以降から補完を開始して、C-pC-n, C-i等で補完候補を選択できるようにしています。

(leaf company
  :ensure t
  :leaf-defer nil
  :blackout company-mode
  :bind ((company-active-map
          ("M-n" . nil)
          ("M-p" . nil)
          ("C-s" . company-filter-candidates)
          ("C-n" . company-select-next)
          ("C-p" . company-select-previous)
          ("C-i" . company-complete-selection))
         (company-search-map
          ("C-n" . company-select-next)
          ("C-p" . company-select-previous)))
  :custom ((company-tooltip-limit         . 12)
           (company-idle-delay            . 0) ;; 補完の遅延なし
           (company-minimum-prefix-length . 1) ;; 1文字から補完開始
           (company-transformers          . '(company-sort-by-occurrence))
           (global-company-mode           . t)
           (company-selection-wrap-around . t))

yasnippet

yasnippetは、テンプレートの挿入を提供しているパッケージです。 こちらもPython以外の言語でも使えます。 Pythonのテンプレートは、yasnippet-snippetsのpython-modeを参照ください。 例えば、ifmと入力してC-iを押下すると、if __name__ == '__main__':が展開されます。

以下の設定では、compay-modeの補完対象として、yasnippetのテンプレートも表示されるようになっています。company-modeと組み合わせることで一つのUIで入力補完とテンプレート挿入ができるようになり、とても便利です。

    (leaf yasnippet
      :ensure t
      :blackout yas-minor-mode
      :custom ((yas-indent-line . 'fixed)
               (yas-global-mode . t)
                                     )
      :bind ((yas-keymap
              ("<tab>" . nil))            ; conflict with company
             (yas-minor-mode-map
              ("C-c y i" . yas-insert-snippet)
              ("C-c y n" . yas-new-snippet)
              ("C-c y v" . yas-visit-snippet-file)
              ("C-c y l" . yas-describe-tables)
              ("C-c y g" . yas-reload-all)))
      :config
      (leaf yasnippet-snippets :ensure t)
      (leaf yatemplate
        :ensure t
        :config
        (yatemplate-fill-alist))
      (defvar company-mode/enable-yas t
        "Enable yasnippet for all backends.")
      (defun company-mode/backend-with-yas (backend)
        (if (or (not company-mode/enable-yas) (and (listp backend) (member 'company-yasnippet backend)))
            backend
          (append (if (consp backend) backend (list backend))
                  '(:with company-yasnippet))))
      (defun set-yas-as-company-backend ()
        (setq company-backends (mapcar #'company-mode/backend-with-yas company-backends))
        )
      :hook
      ((company-mode-hook . set-yas-as-company-backend))
      ))

Poetry.el

Poetry.elはEmacsでpoetryを扱うためのラッパーです。

Poetry.elでEmacsからpoetryを操作できるようになりますが、私は以下のように設定して、もっぱらElpyにpoetryで作成したプロジェクトの仮想環境を認識させるために利用しています。

(leaf poetry
  :ensure t
  :hook ((elpy-mode-hook . poetry-tracking-mode)))

poetry-tracking-modeは、poetryのプロジェクト内のファイルを開いた際に、自動的にそのプロジェクトの仮想環境を有効化できるマイナーモードです。

elpy-modeにフックすることで、elpy起動時にpoetry-tracking-modeを有効化して、Elpyでプロジェクトの仮想環境を参照できるようにしています。

Flycheck

Flycheckは、構文チェッカーです。Elpyでは構文チェッカーとして標準でFlymakeを使用できますが、私の好みでFlycheckを設定しています。

(leaf flycheck
  :ensure t
  :hook (prog-mode-hook . flycheck-mode)
  :custom ((flycheck-display-errors-delay . 0.3))
  :config
  (leaf flycheck-inline
    :ensure t
    :hook (flycheck-mode-hook . flycheck-inline-mode))
  (leaf flycheck-color-mode-line
    :ensure t
    :hook (flycheck-mode-hook . flycheck-color-mode-line-mode)))

FlycheckをElpyで利用するには、上記の設定に加え、elpy-modulesからelpy-module-flymakeを除く必要がありますので、その設定は後述のElpyの設定で行っています。

また、Flycheckの拡張として、以下を導入しています。

Elpy

Elpyは、Emacs上にPython統合開発環境を提供するライブラリです。提供される機能は、以下のようなものがあり、多岐にわたります。

  • プロジェクト管理
  • 自動補完
  • 定義ジャンプ
  • Intaractive Python
  • 構文チェック
  • ドキュメンテーション
  • テンプレート
  • 折り畳み
  • デバッグ
  • テスト
  • リファクタリング

Elpyの設定

事前準備として、ELpyのLintツールで外部コマンドのflake8を利用するため、isortコマンドと同様にpipでインストールするコマンドの管理に従って、pipxでflake8をインストールします。

また、以下の設定でElpyを有効化します。

(leaf elpy
  :ensure t
  :init
  (elpy-enable)
  :config
  (remove-hook 'elpy-modules 'elpy-module-highlight-indentation) ;; インデントハイライトの無効化
  (remove-hook 'elpy-modules 'elpy-module-flymake) ;; flymakeの無効化
  :custom
  (elpy-rpc-python-command . "python3") ;; https://mako-note.com/ja/elpy-rpc-python-version/の問題を回避するための設定
  (flycheck-python-flake8-executable . "flake8")
  :bind (elpy-mode-map
         ("C-c C-r f" . elpy-format-code))
  :hook ((elpy-mode-hook . flycheck-mode))

)
  • Flymakeの無効化 Flycheckを利用しますので、以下の設定でFlymakeを無効化しています。

    (remove-hook 'elpy-modules 'elpy-module-flymake)
  • Elpyのインデントハイライトの無効化 Elpyの標準でもインデントハイライト機能はありますが、最初に設定したhighlight-indent-guidesのほうが私は見やすいので、Elpyのインデントハイライトを以下の設定で無効化しています。

    (remove-hook 'elpy-modules 'elpy-module-highlight-indentation)

Elpyの初回起動時の設定

ElpyではRPC(Remote Procedure Call)のために必要なパッケージ(jedi, yapf, rope等)をインストールする必要があります。

ここまで記載した設定の場合、Elpyの初回起動時に.emacs.d/elpy/rpc-venvに仮想環境を作成して必要なパッケージ(jedi, yapf, rope等)をインストールするか聞かれますので、インストールすることを選択します。

RPC用の仮想環境にjedi等が入っていれば、プロジェクト用の仮想環境にjediをインストールせずとも静的解析ができますので、プロジェクト用の仮想環境を綺麗(プロジェクトに必要なパッケージのみ)に保てます。

もちろん、Poetry.elでプロジェクトの仮想環境をアクティブにしますので、Jediがプロジェクトの仮想環境に入っていなくとも、静的解析の対象はプロジェクトの仮想環境になります。

Elpyの機能

Elpyには多くの機能がありますので、詳細は公式ドキュメントを参照いただくのが良いと思います。ここでは、私がよく使うコマンドを少しだけ紹介します。

  • プロジェクト管理

  • C-c C-f (elpy-find-file) 現在開いているプロジェクトルート配下(仮想環境配下)にあるすべてのファイルに対してのみ、ファイル検索を行います。

  • C-c C-s (elpy-rgrep-symbol) 現在のプロジェクトのファイル群から文字列を検索します。

  • テンプレート yasnippetでテンプレートを挿入しています。 if __name__ == '__main__'はよく忘れるので、ifmでテンプレート挿入できるので、便利です。

  • 自動補完 自動補完はcompany-modeの設定で1文字から補完を開始してyasnippetのテンプレートも補完対象としています。

  • 定義ジャンプ

    • M-. (xref-find-definitions) カーソル上のシンボル(クラス名や関数名等)の定義先に移動します。
    • M-? (xref-find-references) カーソル上のシンボルを呼び出している箇所の候補を表示して、選択後に移動します。
    • M-, (xref-pop-marker-stack) M-.M-?で移動した先から移動前の場所に戻ります。
  • 構文チェック flycheckで動的に構文がチェックされます。

  • リファクタリング

    • C-c C-e (elpy-multiedit-python-symbol-at-point) カーソル上のシンボルのすべての出現箇所を一度に編集します。私や変数名や関数名等を一括変換する際に使用しています。
    • C-c C-r f (elpy-format-code) jediに従って、アクティブなバッファをリファクタリングします。 リージョンが選択されている場合、そのリージョンのみがリファクタリングされます。

終わりに

ここまで、読んでいただき、ありがとうございます。 これで一通りのEmacsでのPython開発環境を構築できました。

今回した設定は、他の統合開発環境ならば標準機能であったり設定が簡単そうだからそっちを使ったほうが良いかなと思うこともあります。

しかし、それでも私がEmacsを使う理由は、自由にカスタマイズできるEmacsが大好きだからです。

今回であれば、自分の好きなように動作する統合開発環境が作れました。 Emacsではelispが読めれば、自分が改善したいなと思うところはかなり改善できますし、今回紹介しましたように色々な方が便利なパッケージを公開してくれています。 カスタマイズが止められない、それがEmacs。

この記事で少しでもEmacsユーザが増え、Emacs界?の盛り上がりに貢献できると嬉しいです。

すべてのEmacsユーザに幸あれ!

謝辞

leaf.elや今回紹介させていただいたパッケージ及び紹介できなったパッケージの作者様、設定ファイルを公開してくださっている方々、いつもありがとうございます。

今回の設定は、色々な方々の設定ファイルを参考にさせていただいている箇所もあるのですが、出典を記録していないため、参考元のサイトが不明となっており、申し訳ございません。

不都合等ありましたら、コメント等でご連絡いただけますと幸いです。

Posted by mako
関連記事
コメント
...