GitHubじゃ!Pythonじゃ!

GitHubからPython関係の優良リポジトリを探したかったのじゃー、でも英語は出来ないから日本語で読むのじゃー、英語社会世知辛いのじゃー

google

yapf – Pythonファイル用のフォーマッタ

投稿日:

Pythonファイル用のフォーマッタ

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実行します。 あらかじめ定義されたスタイル( pep8google )、希望のスタイルを指定する設定ファイルへのパス、またはキーと値のペアの辞書を受け入れます。

設定ファイルは、 [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は次の方法で書式スタイルを検索します:

  1. コマンドラインで指定
  2. 現在のディレクトリまたはその親ディレクトリのいずれかにある.style.yapfファイルの[スタイル]セクション。
  3. 現在のディレクトリまたはその親ディレクトリのいずれかにあるsetup.cfgファイルの[yapf]セクションにあります。
  4. あなたのホームディレクトリの〜/ .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はFormatCodeFormatFile 。これらは以下に説明するいくつかの引数を共有しています:

>>> 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

注: FormatCodefilename引数は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

defclass直前に空の行を挿入して、別のdefclassにネストします。 例えば:

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または後ではなく、分割を好むorTrueに設定し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つのトークンを分割することは決してありません: defxxxxxxxxxxx 、および( これらの地域は「破壊できない」と言われています。 これは、破られない領域内に「分割された」決定(左手の分岐)がないことによって、ツリーに反映されます。

ツリーが作成されたので、最低のコストでツリーを通るパスを見つけることによって、「最良の」フォーマットが何であるかを判断します。

以上です!


YAPFは公式のGoogle製品(実験的またはその他のもの)ではなく、単なるGoogleの所有コードです。







-google
-, ,

執筆者: