メインコンテンツへスキップ

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

··3 分·
Elpy Python Emacs
Makoto Morinaga
著者
Makoto Morinaga
技術メモ、コーディング、環境構築のための個人ノート。
目次

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

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

Pythonの開発環境
#

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

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

Emacsでの開発環境
#

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

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

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

highlight-indent-guides
#

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

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

emacs-lisp
(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は、対応する括弧を強調表示するマイナーモードです。 以下の設定を行うことで、対応する括弧を色分けして表示できますので、対応関係を視認しやすくなります。

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

whitespace-mode
#

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

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

emacs-lisp
(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 に割り当てて、行頭・行末への移動を拡張しています。

emacs-lisp
(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にインストールか、 pipx でインストールしてください。おすすめは管理がしやすいpipxでのインストールです。

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

emacs-lisp
(leaf py-isort :ensure t)

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

company-mode
#

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

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

emacs-lisp
(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で入力補完とテンプレート挿入ができるようになり、とても便利です。

emacs-lisp
(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で作成したプロジェクトの仮想環境を認識させるために利用しています。

emacs-lisp
(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を設定しています。

emacs-lisp
(leaf flycheck
      :ensure t
      :hook (prog-mode-hook . flycheck-mode)
      :custom ((flycheck-display-errors-delay . 0.3)
               (flycheck-indication-mode . 'left-margin)) ;terminalで使うので、fringeではなくmarginに警告を表示
      :config (add-hook 'flycheck-mode-hook #'flycheck-set-indication-mode) ; flycheckのみでmarginを使用
      (leaf flycheck-inline
            :ensure t
            :hook (flycheck-mode-hook . flycheck-inline-mode)))

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

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

Elpy
#

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

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

Elpyの設定
#

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

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

emacs-lisp
(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://mkt3.dev/ja/posts/44d8407e-bd54-4ec0-9ecb-a6c3742a34c2の問題を回避するための設定
      (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を無効化しています。

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

    emacs-lisp
    (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や今回紹介させていただいたパッケージ及び紹介できなったパッケージの作者様、設定ファイルを公開してくださっている方々、いつもありがとうございます。

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

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

関連記事

Elpy RPC Process and Python Version
··1 分
Elpy Python Emacs
Pyenv、pipx、poetryによるPython開発環境(2020)
··1 分
Macos Linux Python
Pythonのデフォルト引数での注意点
··1 分
Python