Github: https://github.com/google/yapf
YAPF
前書き
現在のPythonのフォーマッタの大部分(例えば、autopep8、pep8ify —)は、コードからlintエラーを取り除くために作られています。 これには明らかな制限がいくつかあります。 たとえば、PEP 8ガイドラインに準拠したコードは、再フォーマットされないことがあります。 しかし、それはコードが良く見えるという意味ではありません。
YAPFは異なるアプローチをとっています。 これはDaniel Jasperによって開発された ‘clang-format’に基づいています。 要するに、アルゴリズムはコードを取り、元のコードがスタイルガイドに違反していない場合でも、スタイルガイドに準拠した最適なフォーマットに再フォーマットします。 このアイデアは、Goプログラミング言語用のgofmtツールにも似ています。書式設定に関するすべての聖戦を終わらせる – プロジェクトのコードベース全体がYAPFを介してパイプラインされるだけで、スタイルはプロジェクト全体で一貫しています。すべてのコードレビューでスタイルについて議論する必要はありません。
究極の目標は、YAPFが生成するコードが、スタイルガイドに従っている場合にプログラマが書くコードと同じくらい良いことです。 あなたのコードを守るのに煩わされるものがあります。
このオンラインデモで YAPFをお試しください。
内容
インストール
PyPIからYAPFをインストールするには:
$ pip install yapf
(オプション)Python 2.7を使用していて、マルチプロセッシングを有効にしたい場合:
$ pip install futures
YAPFはまだ「アルファ」段階にあり、リリースされたバージョンは頻繁に変更される可能性があります。 したがって、最新の開発状況を最新の状態に保つ最良の方法は、このリポジトリを複製することです。
YAPFをライブラリではなくコマンドラインツールとして使用する場合は、インストールする必要はありません。 YAPFは、Pythonインタプリタによってディレクトリとして実行されることをサポートします。 YAPFをDIR
クローン/解凍した場合は、次のコマンドを実行することができます。
$ PYTHONPATH=DIR python DIR/yapf [options] ...
Pythonのバージョン
YAPFはPython 2.7および3.6.4+をサポートしています。 (Python 3の機能の中には、3.6.4より前のバージョンのPythonで解析できないものがあることに注意してください)。
YAPFはYAPF自体が実行するバージョンのために有効なPythonになるようにフォーマットするコードを必要とします。 したがって、YAPFでPython 3のコードをフォーマットする場合は、Python 3(Python 2の場合も同様)でYAPFを実行します。
使用法
オプション:
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 the same directory as the source or one of its parent directories (for stdin, the current directory is used). --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
書式設定スタイル
YAPFで使用される書式スタイルは設定可能であり、YAPFの書式設定方法を調整するために使用できる多くの「ノブ」があります。 完全なリストについては、 style.py
モジュールを参照してください。
スタイルを制御するには、–style引き--style
指定して--style
実行します。 あらかじめ定義されたスタイル( pep8
やgoogle
)、希望のスタイルを指定する設定ファイルへのパス、またはキーと値のペアの辞書を受け入れます。
設定ファイルは、 [style]
見出しのkey = value
ペア(大文字小文字を区別しない)の単純なリストです。 例えば:
[style] based_on_style = pep8 spaces_before_comment = 4 split_before_logical_operator = true
based_on_style
設定は、このカスタムスタイルの基になっている定義済みのスタイルを決定します(サブクラス化のように考えます)。
辞書を使ってコマンドラインでも同じことができます。 例えば:
--style='{based_on_style: chromium, indent_width: 4}'
これはchromium
ベースのスタイルをとり、それを修正して4つのスペースインデントを持つようにします。
YAPFは次の方法で書式スタイルを検索します:
- コマンドラインで指定
- 現在のディレクトリまたはその親ディレクトリのいずれかにある.style.yapfファイルの[スタイル]セクション。
- 現在のディレクトリまたはその親ディレクトリのいずれかにあるsetup.cfgファイルの[yapf]セクションにあります。
- あなたのホームディレクトリの〜/ .config / yapf / styleファイル。
これらのファイルが見つからない場合、デフォルトスタイルが使用されます(PEP8)。
例
YAPFが行うことができるフォーマットのタイプの例は、この醜いコードを取ります:
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]
それを次のように再フォーマットします。
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を呼び出すための2つの主なAPIはFormatCode
とFormatFile
。これらは以下に説明するいくつかの引数を共有しています:
>>> 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_config
引数:スタイル名または書式設定の設定を含むファイルへのパス。 Noneを指定した場合は、 style.DEFAULT_STYLE_FACTORY
設定されているデフォルトのスタイルを使用します。
>>> FormatCode("def g():\n return True", style_config='pep8')
'def g():\n return True\n'
A lines
引数:整形したい行(int)、[start、end]のタプルのリスト。 行は1から始まり、インデックスが付けられます。 これは、ファイル全体ではなくコードスニペットを再フォーマットするときにサードパーティのコード(IDEなど)で使用できます。
>>> FormatCode("def g( ):\n a=1\n b = 2\n return a==b", lines=[(1, 1), (2, 3)])
'def g():\n a = 1\n b = 2\n return a==b\n'
print_diff
(bool):再フォーマットされたソースを返す代わりに、フォーマットされたソースをprint_diff
フォーマッタソースに変換するdiffを返します。
>>> print(FormatCode("a==b", filename="foo.py", print_diff=True))
--- foo.py (original)
+++ foo.py (reformatted)
@@ -1 +1 @@
-a==b
+a == b
注: FormatCode
のfilename
引数はdiffに挿入されるもので、デフォルトは<unknown>
です。
FormatFile
は、渡されたファイルとそのエンコーディングと共に、再フォーマットされたコードを返します。
>>> from yapf.yapflib.yapf_api import FormatFile # reformat a file
>>> print(open("foo.py").read()) # contents of file
a==b
>>> FormatFile("foo.py")
('a == b\n', 'utf-8')
in-place
引数は、再フォーマットされたコードをファイルに保存します。
>>> FormatFile("foo.py", in_place=True)
(None, 'utf-8')
>>> print(open("foo.py").read()) # contents of file (now fixed)
a == b
ノブ
-
ALIGN_CLOSING_BRACKET_WITH_VISUAL_INDENT
- 閉じた金具を視覚的に押し込む。
-
ALLOW_MULTILINE_LAMBDAS
- ラムダを複数の行でフォーマットできるようにする。
-
ALLOW_MULTILINE_DICTIONARY_KEYS
-
辞書キーが複数の行に存在することを許可する。 例えば:
x = { ('this is the first element of a tuple', 'this is the second element of a tuple'): value, }
-
ALLOW_SPLIT_BEFORE_DICT_VALUE
- ディクショナリ値の前に分割を許可します。
-
BLANK_LINE_BEFORE_NESTED_CLASS_OR_DEF
-
def
やclass
直前に空の行を挿入して、別のdef
やclass
にネストします。 例えば:class Foo: # <------ this blank line def method(): pass
-
BLANK_LINE_BEFORE_MODULE_DOCSTRING
- モジュールdocstringの前に空白行を挿入してください。
-
BLANK_LINE_BEFORE_CLASS_DOCSTRING
- クラスレベルのドキュメントストリングの前に空白行を挿入します。
-
BLANK_LINES_AROUND_TOP_LEVEL_DEFINITION
-
トップレベル関数とクラス定義を囲む空白行の数を設定します。 例えば:
class Foo: pass # <------ having two blank lines here # <------ is the default setting class Bar: pass
-
COALESCE_BRACKETS
-
連続する括弧を分割しないでください。
DEDENT_CLOSING_BRACKETS
が設定されている場合のみ関連します。 例えば:call_func_that_takes_a_dict( { 'key1': 'value1', 'key2': 'value2', } )
次のように再フォーマットする:
call_func_that_takes_a_dict({ 'key1': 'value1', 'key2': 'value2', })
-
COLUMN_LIMIT
- カラム制限(または最大ライン長)は、
-
CONTINUATION_ALIGN_STYLE
-
継続整列のスタイル。 可能な値は次のとおりです。
-
スペース:継続整列のためにスペースを使用します。 これはデフォルト動作です。
-
固定:継続整列に固定数(CONTINUATION_INDENT_WIDTH)の列(例:CONTINUATION_INDENT_WIDTH / INDENT_WIDTHタブ)を使用する。
-
VALIGN-RIGHT:継続行をインデント文字と垂直に整列させます。 継続行をインデント文字と垂直に並べることができない場合は、少し右(インデント文字が1つ)です。
FIXED
オプションとVALIGN-RIGHT
オプションは、USE_TABS
が有効な場合にのみ使用できます。
-
-
CONTINUATION_INDENT_WIDTH
- 行継続に使用されるインデント幅。
-
DEDENT_CLOSING_BRACKETS
-
括弧で囲まれた式が1行に収まらない場合は、別の行に閉じ括弧を付けてください。 関数定義や呼び出しを含むすべての種類のブラケットに適用されます。 例えば:
config = { 'key1': 'value1', 'key2': 'value2', } # <--- this bracket is dedented and on a separate line time_series = self.remote_client.query_entity_counters( entity='dev3246.region1', key='dns.query_latency_tcp', transform=Transformation.AVERAGE(window=timedelta(seconds=60)), start_ts=now()-timedelta(days=3), end_ts=now(), ) # <--- this bracket is dedented and on a separate line
-
EACH_DICT_ENTRY_ON_SEPARATE_LINE
- 各辞書項目を独自の行に配置します。
-
I18N_COMMENT
- 国際化コメントの正規表現。 このコメントの存在は、コメントが翻訳する文字列の隣にある必要があるため、その行の再フォーマットを停止します。
-
I18N_FUNCTION_CALL
- 国際化関数の呼び出し名。 この関数の存在は、その行のi18nコメントから移動できないため、その行の再フォーマットを停止します。
-
INDENT_DICTIONARY_VALUE
-
辞書のキーと同じ行に収まらない場合は、辞書の値をインデントします。 例えば:
config = { 'key1': 'value1', 'key2': value1 + value2, }
-
INDENT_WIDTH
- インデントに使用する列の数。
-
JOIN_MULTIPLE_LINES
- 短い行を1行にまとめる。 たとえば、1行の
if
文。 -
SPACES_AROUND_POWER_OPERATOR
-
**
たスペースを優先するには、True
に設定しTrue
。 -
NO_SPACES_AROUND_SELECTED_BINARY_OPERATORS
-
選択したバイナリ演算子の周囲にはスペースを入れないでください。 例えば:
1 + 2 * 3 - 4 / 5
値
"*,/"
構成されている場合、次のようにフォーマットされます。1 + 2*3 - 4/5
-
SPACES_AROUND_DEFAULT_OR_NAMED_ASSIGN
- デフォルトまたはキーワード引数の代入演算子の周囲のスペースを優先するには、
True
に設定します。 -
SPACES_BEFORE_COMMENT
- 末尾のコメントの前に必要なスペースの数。
-
SPACE_BETWEEN_ENDING_COMMA_AND_CLOSING_BRACKET
- リストの終わりのカンマと閉じ括弧の間にスペースを挿入します。
-
SPLIT_ARGUMENTS_WHEN_COMMA_TERMINATED
- 引数リストがカンマで終わる場合は、引数の前に分割します。
-
SPLIT_BEFORE_BITWISE_OPERATOR
-
&
前に分割を好むようにするにはTrue
に設定しTrue
|
または^
より後である。 -
SPLIT_BEFORE_CLOSING_BRACKET
- リストまたはdictリテラルが1行に収まらない場合は、閉じ括弧の前に分割します。
-
SPLIT_BEFORE_DICT_SET_GENERATOR
-
辞書の前に分割するか、ジェネレータを設定する(comp_for)。 たとえば、次の前の分割をメモ
for
ます。foo = { variable: 'Hello world, have a nice day!' for variable in bar if variable != 42 }
-
SPLIT_BEFORE_EXPRESSION_AFTER_OPENING_PAREN
- 式を1行に収まらない場合は、式を囲む開始括弧の後に分割します。
-
SPLIT_BEFORE_FIRST_ARGUMENT
- 引数/パラメータリストを分割する場合は、最初の引数の前に分割します。
-
SPLIT_BEFORE_LOGICAL_OPERATOR
- 前に
and
前にand
または後にand
または後ではなく、分割を好むor
はTrue
に設定しTrue
。 -
SPLIT_BEFORE_NAMED_ASSIGNS
- 割り当てを個々の行に分割します。
-
SPLIT_COMPLEX_COMPREHENSION
-
複数の句(例えば、複数の “for”呼び出し、 “if”フィルタ式)を持ち、リフローする必要のあるリスト内包表記とジェネレータ式では、各句を独自の行に分割します。 例えば:
result = [ a_var + b_var for a_var in xrange(1000) for b_var in xrange(1000) if a_var % b_var]
次のような形式に再フォーマットします。
result = [ a_var + b_var for a_var in xrange(1000) for b_var in xrange(1000) if a_var % b_var]
-
SPLIT_PENALTY_AFTER_OPENING_BRACKET
- 開始括弧の直後に分割する場合のペナルティ。
-
SPLIT_PENALTY_AFTER_UNARY_OPERATOR
- 単項演算子の後に行を分割する場合のペナルティ。
-
SPLIT_PENALTY_BEFORE_IF_EXPR
-
if
式の直前で分割するif
のペナルティ。 -
SPLIT_PENALTY_BITWISE_OPERATOR
-
&
、|
周りに線を分割することのペナルティ|
、および^
演算子。 -
SPLIT_PENALTY_COMPREHENSION
- リストの理解やジェネレータの表現を分割する場合のペナルティ。
-
SPLIT_PENALTY_EXCESS_CHARACTER
- 列制限を超える文字のペナルティ。
-
SPLIT_PENALTY_FOR_ADDED_LINE_SPLIT
- アンラップされた行に行分割を追加することによって発生するペナルティ。 ラインスプリットが増えるほどペナルティは高くなります。
-
SPLIT_PENALTY_IMPORT_NAMES
-
import as
リストを名前import as
分割するというペナルティ。 例えば:from a_very_long_or_indented_module_name_yada_yad import (long_argument_1, long_argument_2, long_argument_3)
次のような形式に再フォーマットします。
from a_very_long_or_indented_module_name_yada_yad import ( long_argument_1, long_argument_2, long_argument_3)
-
SPLIT_PENALTY_LOGICAL_OPERATOR
-
and
andとor
演算子のまわりで行を分割する場合のペナルティ。 -
USE_TABS
- インデントにはタブ文字を使用します。
(潜在的に)よくある質問
なぜYAPFは私のすばらしいフォーマットを破壊するのですか?
YAPFは、書式設定が正しくなるように非常に努力しています。 しかし、いくつかのコードでは、手作業で書かれたものほどうまくいかないでしょう。 特に、大きなデータリテラルは、YAPFの下でひどく崩壊することがあります。
これの理由は多岐に渡ります。 要するに、YAPFは単なる開発を助けるツールです。 それはスタイルガイドと一致するように書式を設定しますが、それは可読性と同じではありません。
この状況を緩和するためにできることは、何かを再フォーマットするときにYAPFが無視すべき地域を示すことです:
# yapf: disable
FOO = {
# ... some very large, complex data literal.
}
BAR = [
# ... another large data literal.
]
# yapf: enable
次のように1つのリテラルの書式設定を無効にすることもできます:
BAZ = {
(1, 2, 3, 4),
(5, 6, 7, 8),
(9, 10, 11, 12),
} # yapf: disable
素敵な括弧で囲まれた閉じ括弧を保持するには、自分のスタイルでdedent_closing_brackets
を使用します。 この場合、関数定義や呼び出しを含むすべての角括弧は、そのスタイルを使用することに注意してください。 これにより、フォーマットされたコードベース全体に一貫性がもたらされます。
既存のツールを改善してみませんか?
clang形式の再フォーマットアルゴリズムを使用したかったのです。 これは非常に強力で、可能な限り最良のフォーマットを考え出すように設計されています。 既存のツールは、異なる目標を念頭に置いて作成され、clang形式のアルゴリズムを使用して変換するためには大幅な変更が必要でした。
自分のプログラムでYAPFを使用できますか?
してください! YAPFは、ライブラリとしてだけでなく、コマンドラインツールとしても使用できるように設計されています。 つまり、ツールやIDEプラグインは自由にYAPFを使用できます。
ゴリー詳細
アルゴリズム設計
YAPFの主なデータ構造は、 UnwrappedLine
オブジェクトです。 FormatToken
のリストを保持します。これは、列の制限がない場合は1行に配置します。 式ステートメントの途中のコメントである例外は、行を複数の行に強制的に書式設定します。 フォーマッタは、一度に1つのUnwrappedLine
オブジェクトで動作します。
UnwrappedLine
は、通常、その前後の行の書式設定には影響しません。 2つ以上のUnwrappedLine
を1つの行に結合するアルゴリズムの一部があります。 たとえば、短い本体のif-then文を1行に置くことができます。
if a == 42: continue
YAPFのフォーマットアルゴリズムは、アルゴリズムの解空間として機能する重み付きツリーを作成します。 ツリー内の各ノードは、フォーマットの決定の結果を表します。つまり、分割するかどうかはトークンの前に分割するかどうかを表します。 各書式設定の決定には、それに関連するコストがあります。 したがって、コストは2つのノード間のエッジで実現されます。 (実際には、重み付きツリーには別々のエッジオブジェクトがないため、ノード自体にコストがかかります)。
たとえば、次のPythonコードスニペットを使用します。 この例では、line(1)が列の制限の制限に違反し、再フォーマットする必要があると仮定します。
def xxxxxxxxxxx(aaaaaaaaaaaa, bbbbbbbbb, cccccccc, dddddddd, eeeeee): # 1
pass # 2
行(1)に対して、アルゴリズムは、各ノード( FormattingDecisionState
オブジェクト)が、トークンの前に分割することを決定した場合、そのトークンの行の状態であるツリーを構築する。 注: FormatDecisionState
オブジェクトは値によってコピーされるため、グラフの各ノードは一意であり、変更が他のノードに影響することはありません。
発見的手法は、分割のコストを決定するために使用されます。 ノードはトークンの挿入までツリーの状態を保持するので、分割の決定がスタイル要件の1つに違反するかどうかを簡単に判断できます。 例えば、ヒューリスティックは、前のトークンと追加されたトークンとの間で分割しないときに、エッジに追加のペナルティを適用することができる。
行を分割したくない場合があります。そうすることは常に有害なものになります(つまり、まれに望ましくないバックスラッシュ改行が必要になる)。 line(1)では、最初の3つのトークンを分割することは決してありません: def
、 xxxxxxxxxxx
、および(
。 これらの地域は「破壊できない」と言われています。 これは、破られない領域内に「分割された」決定(左手の分岐)がないことによって、ツリーに反映されます。
ツリーが作成されたので、最低のコストでツリーを通るパスを見つけることによって、「最良の」フォーマットが何であるかを判断します。
以上です!
YAPFは公式のGoogle製品(実験的またはその他のもの)ではなく、単なるGoogleの所有コードです。