もた日記

くだらないことを真面目にやる

Pythonメモ : yapfでソースコードの整形(フォーマット)

yapf


github.com

yapfを使用するとPythonコードを整形(フォーマット)できるので試してみる。yapfはYet Another Python Formatterの略のようだ。

f:id:wonder-wall:20170903223207p:plain


インストール


pipでインストールできるので下記コマンドを実行。

$ pip install yapf

ただし、yapfは頻繁に変更が加えられているということなので今回はGitHubのリポジトリをインストールしてみる。

$ pip install git+https://github.com/google/yapf

ヘルプメッセージ

$ yapf --version
yapf 0.17.0
$ yapf --help
usage: yapf [-h] [-v] [-d | -i] [-r | -l START-END] [-e PATTERN]
            [--style STYLE] [--style-help] [--no-local-style] [-p] [-vv]
            [files [files ...]]

Formatter for Python code.

positional arguments:
  files

optional arguments:
  -h, --help            show this help message and exit
  -v, --version         show version number and exit
  -d, --diff            print the diff for the fixed source
  -i, --in-place        make changes to files in place
  -r, --recursive       run recursively over directories
  -l START-END, --lines START-END
                        range of lines to reformat, one-based
  -e PATTERN, --exclude PATTERN
                        patterns for files to exclude from formatting
  --style STYLE         specify formatting style: either a style name (for
                        example "pep8" or "google"), or the name of a file
                        with style settings. The default is pep8 unless a
                        .style.yapf or setup.cfg file located in one of the
                        parent directories of the source file (or current
                        directory for stdin)
  --style-help          show style settings and exit; this output can be saved
                        to .style.yapf to make your settings permanent
  --no-local-style      don't search for local style definition
  -p, --parallel        Run yapf in parallel when formatting multiple files.
                        Requires concurrent.futures in Python 2.X
  -vv, --verbose        Print out file names while processing


使い方


下記コードを整形する場合を考える。

$ cat test.py
x = {  'a':37,'b':42,

'c':927}

y = 'hello ''world'
z = 'hello '+'world'
a = 'hello {}'.format('world')
class foo  (     object  ):
  def f    (self   ):
    return       37*-+2
  def g(self, x,y=42):
      return y
def f  (   a ) :
  return      37+-+a[42-x :  y**3]

yapfコマンドでコードを指定すれば整形結果が標準出力される。

$ yapf test.py
x = {'a': 37, 'b': 42, 'c': 927}

y = 'hello ' 'world'
z = 'hello ' + 'world'
a = 'hello {}'.format('world')


class foo(object):
    def f(self):
        return 37 * -+2

    def g(self, x, y=42):
        return y


def f(a):
    return 37 + -+a[42 - x:y**3]

yapf -d test.pyとすれば差分が表示され、yapf -i test.pyとすれば整形結果でファイルが上書きされる。ディレクトリを再帰的に処理したい場合は-rオプションを指定する。

READMEには以下のようにモジュールとして使用する方法も書いてある。

>>> from yapf.yapflib.yapf_api import FormatCode  # reformat a string of code

>>> FormatCode("f ( a = 1, b = 2 )")
'f(a=1, b=2)\n'


整形スタイル

整形スタイルの項目と、現在の設定は--style-helpオプションで確認できる。

$ yapf --style-help
[style]
# Align closing bracket with visual indentation.
align_closing_bracket_with_visual_indent=True

# Allow dictionary keys to exist on multiple lines. For example:
#
#   x = {
#       ('this is the first element of a tuple',
#        'this is the second element of a tuple'):
#            value,
#   }
allow_multiline_dictionary_keys=False

 ...省略...

ソースコードを見てみるとpep8, chromium, google, facebookをベーススタイルとして指定でき、デフォルトではpep8がベーススタイルとして設定されている。 ベーススタイルは--styleオプションで変更できる。

$ yapf --style-help --style=pep8 | grep "column_limit"
column_limit=79
$ yapf --style-help --style=google | grep "column_limit"
column_limit=80

ベーススタイルを変更した上で、個別に項目を変更したい場合は以下のようにする。

$ yapf --style-help --style='{based_on_style: google, column_limit: 120}' | grep column_limit
column_limit=120

スタイルの優先順序は、

  1. コマンドラインで指定
  2. カレントディレクトリまたは親ディレクトリの.style.yapf[style]セクション
  3. カレントディレクトリまたは親ディレクトリのsetup.cfg[yapf]セクション
  4. ホームディレクトリの~/.config/yapf/style

とのこと。 .style.yapfの場合は、以下のようにキーと値の組み合わせを記述したファイルをカレントディレクトリまたは親ディレクトリに置けば設定が反映される。

[style]
based_on_style = google
column_limit = 120
indent_width = 2


整形スタイルの各項目


整形スタイルの各項目はyapf --style-helpで確認するか、READMEのここを参照。 pep8の場合のデフォルト値は下記表のようになっている。

設定 pep8
ALIGN_CLOSING_BRACKET_WITH_VISUAL_INDENT True
ALLOW_MULTILINE_LAMBDAS False
ALLOW_MULTILINE_DICTIONARY_KEYS False
ALLOW_SPLIT_BEFORE_DICT_VALUE True
BLANK_LINE_BEFORE_NESTED_CLASS_OR_DEF False
BLANK_LINE_BEFORE_CLASS_DOCSTRING False
COALESCE_BRACKETS False
COLUMN_LIMIT 79
CONTINUATION_INDENT_WIDTH 4
DEDENT_CLOSING_BRACKETS False
EACH_DICT_ENTRY_ON_SEPARATE_LINE True
I18N_COMMENT ‘’
I18N_FUNCTION_CALL ‘’
INDENT_DICTIONARY_VALUE False
INDENT_WIDTH 4
JOIN_MULTIPLE_LINES True
SPACE_BETWEEN_ENDING_COMMA_AND_CLOSING_BRACKET True
SPACES_AROUND_POWER_OPERATOR False
NO_SPACES_AROUND_SELECTED_BINARY_OPERATORS set()
SPACES_AROUND_DEFAULT_OR_NAMED_ASSIGN False
SPACES_BEFORE_COMMENT 2
SPLIT_ARGUMENTS_WHEN_COMMA_TERMINATED False
SPLIT_BEFORE_BITWISE_OPERATOR True
SPLIT_BEFORE_DICT_SET_GENERATOR True
SPLIT_BEFORE_FIRST_ARGUMENT False
SPLIT_BEFORE_LOGICAL_OPERATOR True
SPLIT_BEFORE_NAMED_ASSIGNS True
SPLIT_PENALTY_AFTER_OPENING_BRACKET 30
SPLIT_PENALTY_AFTER_UNARY_OPERATOR 10000
SPLIT_PENALTY_BEFORE_IF_EXPR 0
SPLIT_PENALTY_BITWISE_OPERATOR 300
SPLIT_PENALTY_EXCESS_CHARACTER 4500
SPLIT_PENALTY_FOR_ADDED_LINE_SPLIT 30
SPLIT_PENALTY_IMPORT_NAMES 0
SPLIT_PENALTY_LOGICAL_OPERATOR 300
USE_TABS False

プラグイン


プラグインページではVim, Emacs, Sublime Textなどで使用する方法が紹介されている。