Black: Python のソースコードを自動整形するツール
Python のコードを自動整形するツールでは、autopep8 と YAPF が有名です。 これらを使えば、コードのスタイルについて気を回さないでよくなり、生産性が上がります。
最近、2018 年に登場したばかりの Black が使われるのを見かけるようになりました。 Black は現時点ではベータ版なのが注意点ですが、十分に使えそうです。 ここではデフォルト設定の YAPF と比べつつ、Black についてまとめてみようと思います。
Black の特徴
自動整形ツールは基本的に pycodestyle などのチェッカーでエラーにならないように整形します。 しかし、Black はそれに縛られず、より細部に渡って PEP8 に準拠したスタイルに整形します。 YAPF は設定できる項目が多いのが特徴ですが、Black は一行あたりの文字数だけしか設定を変更できません。 できるだけスタイルを統一させ、開発者がコードの内容に集中できることを目的にしています。
自分では確認していないですが、Black の採用しているアルゴリズムは YAPF のものよりも高速なようです。 また、YAPF で 1 回整形した後に、もう 1 度 YAPF で整形すると、何も手を加えていないのに結果が変わることがあるそうです。 Black にはそういうことがないとのことです。
Black は pip
でインストールできます。
なお、使用には Python 3.6.0 以上が要求されています。
そのため、Python3.6 で追加された構文にも対応しています。
pip install black
Black は主要なエディタ、IDE で使うことができます。
VS Code なら設定の python.formatting.provider
項目で Black を選べるようになっています。
Emacs では Elpy などで使えます。
YAPF と Black による整形結果の比較
デフォルト設定の YAPF と Black による整形結果を比較します。
比較 1
# 元のコード 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 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]
# Black 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]
Black では文字列リテラルの '
が "
に変換されます。
これは 1 種類の形式で統一されていた方が美しいという考えからだそうです。
PEP 257 でドキュメンテーション文字列では """
が推奨されているなどの理由から "
の方に統一されます。
英語キーボードだと '
の方が押しやすいので、それで入力して最後に Black で変換すると楽です。
一応、コマンドラインではこれを無効にする --skip-string-normalization
オプションが用意されています。
シーケンスのスライスの :
の周りにスペースが入ります。
この :
は、PEP8 では二項演算子と同じように扱われるべきとされています。
そのため、Black ではスペースを入れることになっています。
なお同様に、べき乗の **
の周りにもスペースが入ります。
Flake8 などのチェッカーは E203 whitespace before ':'
と警告を出します。
そのため、設定ファイルで E203 を無視するように設定する必要があります。
比較 2
# 元のコード def very_important_function(template: str, *variables, file: os.PathLike, debug: bool = False): """Applies `variables` to the `template` and writes to `file`.""" with open(file, 'w') as f:
# YAPF def very_important_function(template: str, *variables, file: os.PathLike, debug: bool = False): """Applies `variables` to the `template` and writes to `file`.""" with open(file, 'w') as f: ...
# Black def very_important_function( template: str, *variables, file: os.PathLike, debug: bool = False ): """Applies `variables` to the `template` and writes to `file`.""" with open(file, "w") as f: ...
Black は 1 行の文字数を、80 の 1 割増しである 88 文字に制限します。 何か科学的根拠があるわけではないですが、79 文字制限だと長い行が 3 行以上に渡って改行されることが多い感じがあったので 88 文字にしたそうです。 Raymond Hettinger - Beyond PEP 8 での 90 文字制限が良いという話などにも影響されているとのことです。 また、Black は長い行の改行では 1 レベルインデントするように揃えます。
Flake8 などで E501 を無視するか、1 行の最大文字数を増やすように設定する必要があります。
比較 3
# 元のコード income = (gross_wages + taxable_interest + (dividends - qualified_dividends) - ira_deduction - student_loan_interest)
# YAPF income = (gross_wages + taxable_interest + (dividends - qualified_dividends) - ira_deduction - student_loan_interest)
# Black income = ( gross_wages + taxable_interest + (dividends - qualified_dividends) - ira_deduction - student_loan_interest )
一行が長い場合、Black は二項演算子の前で改行します。
これだと Flake8 などでは W503 line break before binary operator
と警告が出ることがあります。
W503 も設定ファイルで無視しておきます。
比較 4
# 元のコード def example(session): result = ( session.query(models.Customer.id).filter( models.Customer.account_id == account_id, models.Customer.email == email_address ).order_by(models.Customer.id.asc()).all())
# YAPF def example(session): result = (session.query(models.Customer.id).filter( models.Customer.account_id == account_id, models.Customer.email == email_address).order_by( models.Customer.id.asc()).all())
# Black def example(session): result = ( session.query(models.Customer.id) .filter( models.Customer.account_id == account_id, models.Customer.email == email_address, ) .order_by(models.Customer.id.asc()) .all() )
Black は ,
で区切られた式を改行した場合などには、末尾が ,
になるようにします。
また、属性参照の .
演算子の前は改行箇所の対象になります。
自動整形の無効範囲
# fmt: off
と # fmt: on
で挟まれた範囲は自動整形されません。
# fmt: off list_ = { [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], } # fmt: on
pyproject.toml
Black は、そのプロジェクトで使うコマンドラインオプションの値を pyproject.toml から読み込むことができます。 そのため、パッケージングに pyproject.toml を用いる Poetry などと相性がいいです。
まとめ
Black は設定項目は少なく、そのスタイルが気に入らないと使えません。 一方 YAPF は設定の自由度が高く、Black のスタイルに近づけることもできます。 そのため、Black と YAPF のどちらを使うかは好みの問題だと思います。 個人的には Black のスタイルは悪くないと思うので、安定版になったら乗り換えるつもりです。