Python 1 行が 79 文字以内である理由と 1 行を最大 79 文字以内に抑える方法

1 行が 79 文字以内である理由



エディタに勝手に改行させないため。



コードレビューする際に、エディタで勝手に改行されると読みづらくなるので。

この 1 行の文字数の制限は、1 行が 80 文字のエディタが折り返して表示する機能を避けるために選定されたものである。
PEP 8 - Style Guide for Python Code


ちなみにこの 80 という数字はパンチカードから来ているのでは、という話を聞いたことがあります。

1928年、IBMは縦長の長方形の穴を採用し、80欄で各欄に12のパンチ位置があり、1欄(コラム)で1文字を表す形式のカードを設計した[23]。
パンチカード | Wikipedia


◯ どのくらいの温度感なの?

結構厳しい制限ですが、どれくらいの温度感なのでしょうか? Python で最初からはいっているモジュールで Python で書かれたものは、基本的には 79 文字以内に限定されています。

Python の標準ライブラリは保守的であり、79 文字以内に限定されていなければなりません(また docstring や コメントは 72 文字以内に限定されています。)
The Python standard library is conservative and requires limiting lines to 79 characters (and docstrings/comments to 72).
PEP 8 - Style Guide for Python Code


ここで言う標準ライブラリとは import すると使える機能のうち Python で書かれたコードを指しています。具体的には下記の URL 先のコードです。
cpython/Lib - GitHub


◯ 和訳

PEP 8 のうち Maximum Line Length 抜粋、和訳しました。

1行の最大行数 - PEP 8
Maximum Line Length - PEP 8

すべての行を最大 79 文字に制限する。
Limit all lines to a maximum of 79 characters.

コードの後に続く docstring やコメントのような構造的な制限の少ない長いブロックのテキストについては、1 行の長さは 72 字以内にするべきだ。
For flowing long blocks of text with fewer structural restrictions (docstrings or comments), the line length should be limited to 72 characters.

エディタのウィンドウを表示する際に必要な幅を制限できれば、複数のファイルを並べることが可能になり、2 つのバージョンのコードを隣接して左右に並べて、コードリビューツールを使うときに効果的である。
Limiting the required editor window width makes it possible to have several files open side-by-side, and works well when using code review tools that present the two versions in adjacent columns.

大抵のツールが提供する、デフォルトで長い 1 行を折り返して表示してくれる機能(wrapping)は、コードの見た目の構造を破壊し、より理解を困難なものにする。
The default wrapping in most tools disrupts the visual structure of the code, making it more difficult to understand.

この 1 行の文字数の制限は、1 行が 80 文字のエディタが折り返して表示する機能を避けるために選定されたものである。もし、たとえツールが目印として 1 行の文字を複数行で折り返し表示したときに、最後の文字に印を置いてくれるような機能がったとしても勝手に折り返されるのを避けるために1行を 79 文字に制限するべきである。
The limits are chosen to avoid wrapping in editors with the window width set to 80, even if the tool places a marker glyph in the final column when wrapping lines.

いくつかのウェブベースのツールは、自動的に行を折り返してくれるようなことは、全くしてくれないかもしれない。
Some web based tools may not offer dynamic line wrapping at all.

チームによっては、もっと長い行で書きたいと強く思うかもしれない。 この問題について同意に達することができるチームだけが、あるいはそのチームがおもに、保守運営するようなコードに対しては 名目的な文字数の上限を 80 文字から 100 文字に引き上げてもいい(実質な文字数の上限は 99 文字の長さまで伸ばしても良いことになる)、 ただし、コメントと docstring は 72 文字以内のままであると言う条件には、従わねばなりません。
Some teams strongly prefer a longer line length. For code maintained exclusively or primarily by a team that can reach agreement on this issue, it is okay to increase the nominal line length from 80 to 100 characters (effectively increasing the maximum length to 99 characters), provided that comments and docstrings are still wrapped at 72 characters.

Python の標準ライブラリは保守的であり、79 文字以内に限定されていなければなりません (また docstring や コメントは 72 文字以内に限定されています。)
The Python standard library is conservative and requires limiting lines to 79 characters (and docstrings/comments to 72).

1行を複数行に分けて書く方法として望ましいものは、 括弧 parentheses ()、中括弧 braces {}、大括弧 brackets [] を使えば Python では暗黙的に行を継続できることを活用することです。
The preferred way of wrapping long lines is by using Python's implied line continuation inside parentheses, brackets and braces.

長い行は、括弧で括られた式によって、複数行に分割させることができます。 バックスラッシュで行を継続させるよりも、括弧を使って行を継続させた方が望ましいです。
Long lines can be broken over multiple lines by wrapping expressions in parentheses. These should be used in preference to using a backslash for line continuation.

バックスラッシュは、時と場合によっては、適切かもしれません。 例えば、長い multiple with-statements は暗黙的に行を継続することができないので バックスラッシュを使うことが望ましいです。
Backslashes may still be appropriate at times. For example, long, multiple with-statements cannot use implicit continuation, so backslashes are acceptable:

with open('/path/to/some/file/you/want/to/read') as file_1, \
     open('/path/to/some/file/being/written', 'w') as file_2:
    file_2.write(file_1.read())

(multiline with-statement のインデントに関するさらなる考察は、上記で議論された multiline if-statement を参照してください。)
(See the previous discussion on multiline if-statements for further thoughts on the indentation of such multiline with-statements.)

他の例としては assert 文があります。
Another such case is with assert statements.

行を折り返した場合は適切にインデントがなさるようにしてください。 Make sure to indent the continued line appropriately.

Markdown の改行について

全く話がそれますが Markdown では改行は、システムに任せると言う考えなので、Python のコーディング規約とは真逆ですね。 面白いので Markdown の設計背景の文章を引用してみたいと思います。

段落と改行 - Markdown: 構文
PARAGRAPHS AND LINE BREAKS - Markdown: Syntax

段落とは、単純に1つまたはそれ以上の連続した文章の行を指していて、段落は1行または複数行の空白によって分けれらています。(空白の行とは、全ての空白の行のように見える行を指しています ー 空白またはタブだけの行は、空白とみなされます。)通常の段落は、空白またはタブでインデントされてはいけません。
A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. (A blank line is any line that looks like a blank line — a line containing nothing but spaces or tabs is considered blank.) Normal paragraphs should not be indented with spaces or tabs.

「1つまたは2つ以上の連続した文章の行」の意味合いを、 Markdown では「強制的に改行した」段落として捉えています (ワイの注釈: 単純に改行はされてるだけなら、Markdown ではそれを1つの段落として解釈します。)。 これはほとんどの text ファイルから HTML ファイルへの書式整形を行うツールと大きく異なります。 (Movable Type の “Convert Line Breaks” オプションも含めて) ほとんどの書式ツールというのは、段落内の全ての改行文字を <br /> tag に変換します。
The implication of the “one or more consecutive lines of text” rule is that Markdown supports “hard-wrapped” text paragraphs. This differs significantly from most other text-to-HTML formatters (including Movable Type’s “Convert Line Breaks” option) which translate every line break character in a paragraph into a <br /> tag.

あなたが Markdown を使用していて <br /> tag を挿入したいときは、行を2つまたはそれ以上の半角スペースを入力した後に改行を入力してください。
When you do want to insert a <br /> break tag using Markdown, you end a line with two or more spaces, then type return.

もちろん、<br /> を生成させるために、このようにして空白を2文字以上いれることは手間となります。しかし、単純に「全ての改行は <br /> だという。」という考えでは、 Markdown を活用できません。
Yes, this takes a tad more effort to create a <br />, but a simplistic “every line break is a <br />” rule wouldn’t work for Markdown.

Markdown の email 形式の blockquoting と multi-paragraph list items で書いて、それを行末に達すると強制的に改行される機能(hard breaks)を持ったもので表示させれば、うまく書式整形されて見栄えも良くなります (ワイの注釈: このあと例を示します)
Markdown’s email-style blockquoting and multi-paragraph list items work best — and look better — when you format them with hard breaks.


blockquoting > と multi-paragraph list items 1.2.3.を同時に使って書いたものは...

> 1. 
>   una ex parte flumine Rheno latissimo atque altissimo,
>   qui agrum Helvetium a Germanis dividit;
>
> 2. 
>   altera ex parte monte Iura altissimo,
>   qui est inter Sequanos et Helvetios;
>
> 3. 
>   tertia lacu Lemanno et flumine Rhodano,
>   qui provinciam nostram ab Helvetiis dividit.
>
> [Commentarii de bello Gallico](http://bit.ly/2tWqecG)


行末に達すると強制的に改行される機能(hard breaks)によって、次のように表示されます。

  1. una ex parte flumine Rheno latissimo atque altissimo, qui agrum Helvetium a Germanis dividit;

  2. altera ex parte monte Iura altissimo, qui est inter Sequanos et Helvetios;

  3. tertia lacu Lemanno et flumine Rhodano, qui provinciam nostram ab Helvetiis dividit.

Commentarii de bello Gallico


これがなぜ "上手くいく" と表現されているのでしょうか?

1. 幅が狭い画面で表示する場合...

基本的にメールなどのテキスト入力では改行を意識して書かないといけません。 改行しないと横にだだっ広い、ひどい文章になってしまいます。

しかし、もし改行を任せることができるなら、 システムにやってもらった方がいいかなと思います。

書いた側のテキスト入力画面よりも小さいときには、その方が絶対に有利です。 例えば PC で書いたものをスマホで表示させるようなケースです。

2. 幅が広い画面で表示する場合...

反対に、書いた環境よりも、さらに横にだだっ広い環境で表示させるときは、読みづらくなってしまいますが。 ただ、テキストエディタよりも、横にだだっ広い表示環境ってそんなにないのかな.. と。

まとめ

Markdown は横に広いテキスト入力形式の画面から ブラウザなどの横が制限された画面への変換形式なのかなと思ったりもします。

インデントが 4 文字のスペースである理由、2 文字ではない理由



Flat is better than nested.
ネストは浅い方が良い
PEP 20 - The Zen of Python

インデントには 4 文字の半角スペースを使ってな
Use 4 spaces per indentation level.
PEP 8 - Style Guide for Python Code


ただでさえ 79 文字で制限されているのに 4 文字でインデント作れとか、どんだけ縛りプレイなんや、とか思ったりもしました。

逆にインデントを深くするなということなのかなと、最近、個人的に思うようになりました。 インデントが深くなった処理は関数にまとめて浅くすることができます。

f:id:domodomodomo:20180805163623j:plain 尊敬するフリをして煽ってくるスタイル

1 行を最大 79 文字以内に抑える方法

1 行 79 文字と言う制限が、実は結構重くて最初は無視するような設定にしようかと思っていたのですが、最近は遵守しています。いくつか回避方法があります。

1. バックスラッシュ \ を使う。

def eq(rectangle_a, rectangle_b):
    """2つの長方形が同じかどうかを確認する関数"""
    return \
        rectangle_a.x1 == rectangle_b.x1 and \
        rectangle_a.y1 == rectangle_b.y1 and \
        rectangle_a.x2 == rectangle_b.x2 and \
        rectangle_a.y2 == rectangle_b.y2


PEP 8 でもバックスラッシュ \ よりも括弧 ( ) を使ってねと書かれてましたが、 実は Guido は、バックスラッシュ \ は使って欲しくないそうです。 理由は書かれていないですが、確かに汚いですね。 かわりに 括弧 ( ) を使うように言っています。

· continued lines or strings with \
- use (...) continuation and/or string literal concatenation
Python Regrets

2. 括弧 ( ) を使う

括弧内なら改行が許容されます。


def eq(rectangle_a, rectangle_b):
    return all(
        rectangle_a.x1 == rectangle_b.x1,
        rectangle_a.y1 == rectangle_b.y1,
        rectangle_a.x2 == rectangle_b.x2,
        rectangle_a.y2 == rectangle_b.y2,
    )


文字列連結では、こんな書き方ができます。

# 1. str として格納される。tuple に格納される訳ではない。
# 2. + 演算子はいらない。
allowed_chars= ('abcdefghijklmnopqrstuvwxyz'
                'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')

Python で pep-8 の「E501 line too long」にしないための...
line too longの対処法(メソッドが連続する場合)

3. 変数を分ける

個人的にはこんな感じで別の変数に一旦格納して2行に分けたりもします。これはこれで変数が増えてしまうのですが..。

# 前
supplier = Supplier.objects.filter(category=supplier_category).order_by('phonetic')

# 後
supplier = Supplier.objects.filter(category=supplier_category)
supplier_ordered_by_phonetic = supplier.order_by('phonetic')

4. 関数にする

例えばネストが深くなってしまった時は関数にしてネストから外してしまえば良いと思います。

5. 抽象化の度合いあげる。

例えば、iterator, descriptor, operator overload を使って短くすることができます。

>>> for member in team.member_list:
...     print(member)
川島 永嗣
香川 真司
長谷部 誠 
>>>
>>> for member in team:
...     print(member)
川島 永嗣
香川 真司
長谷部 誠 
>>>

Python のイテレータ

6. 変数名を短くしちゃう?

もし短くしても、他人が理解できるなら、短い方が良いと思います。

6.1. 変数名の長さによるメリット、デメリット

変数名が長い

  • メリット 読めばわかる。
  • デメリット 読みたくない(読めばわかるんだけど...)

変数名が短い

  • デメリット 読んでもわからない
  • メリット 読んでもいいかな、と思う


もちろん変数名が長い方が、理解できるコードになります。ただ、その分コードが長くなって、読むのが精神的に辛いコードになってしまいます。

6.2. 変数名の長さを決める判断基準
  1. 使われる頻度
  2. スコープの大きさ
  3. コードそのものの理解のしやすさ。

使われる頻度が頻繁にあるなら、たとえスコープが広くても、短くていいと思います。例えば、組込関数や組込型なんかは、総じて短い名前です。

コードの理解のしやすさと言うのは、コードそのものが、よく使われるアルゴリズムなど、わかりきったものであれば、ごく短い変数名を使う方が良いと思います。反対に、業務ロジックなどが書かれたものであれば、ある程度詳細に書いた方が望ましいと感じます。

6.3. なんでこんなことを考えたか

Qiita の記事ですごく感情的なコメントが多い記事を見つけました。
なぜfor文は禁止なのか?関数型記述のススメ - Qiita

なぜこんなに感情的になったのかは、いくつか要因があると思います。

主たる要因は for 文を禁止にはできないと言うことかなと思います。おそらく Python に言いかえれば、リスト内包表記は、読みやすいと言うのに近い主張かなと感じました。しかし、リスト内包表記が読みやすいからと言って for 文がいらないわけではないと思います。

副次的な要因としては、関数名が長いことだと思います。長い関数名には、圧迫感があります。もちろん、長い関数名そのものには、全く問題がないのですが、"100 以下の偶数の合計を計算する" と言う、あまりにもわかりきった処理に対して長い関数名を当ててしまっています。

それがおそらく "冗長すぎるやろ" という認識を起こしてしまったのだと思います。この関数が、業務ロジックのような、ちゃんと読まないとわからないコードだったら話は違ったと思うのですが。

// 元のコード
const from0To100Array = Array.from(Array(100).keys());
const isEvenNumber = i => i % 2 === 0;
const addAll = (total, i) => total + i;
const totalOfEvenNumberUnder100 = from0To100Array.filter(isEvenNumber).reduce(addAll);


変数名を短くすると精神的な負荷が、低いコードになります。 可読性が上がるかどうかは別にして。

  1. n = 100 にして抽象度合いをあげる。
  2. isEvenNumber の Number は自明なので削除
  3. addAll は All を add してないので削除
// 少し書き換えてみる。
const n = 100
const array = Array.from(Array(n).keys());
const isEven = i => i % 2 === 0;
const add = (a, b) => a + b;
const sumOfEvenArray = array.filter(isEven).reduce(add);


① 適用できる範囲が本来はもう少し限定されるのに for 文はいらないと言ってしまったのと、② 取り上げる例を、もう少し業務ロジック的なもの、変数名が長くなるものを取り上げていれば、温度感も、もう少し違ったのかなと思ったりもします。


書籍 Readable Code では変数は、初めての人が読んでわかるかどうか?を基準にして決めることを紹介していました。普段書いてるよりも、長くてもいいとは思うのですが、これだけ温度感が上がってしまうということは、おそらく、あの界隈の人たちには、初めてでも十分にわかるコードだったのかなと思ったりもします。


この記事も長い関数名を書いて、若干過熱気味になったブコメ欄。
コメントのいらないプログラムの書き方 - 炎上気味のブコメ欄
コメントのいらないプログラムの書き方 - 移転したブログ本文

テストコードは仕方ないかなと思ったりします。 テストコードの関数なんて他から呼び出さないから。 頻繁に呼び出すなら辛いかもしれませんが。 それに、改修があった時に、 古いコメントは削除だけして更新をサボってしまうかもしれないけど、 関数名はちゃんと更新しそうだから。

ワイもコメントを書かなくてもいいくらいの会社で平和にのんびりやりたいなと思ったり、 思わなかったり。名前が長いと炎上しやすかったりするのかなと思ったり、思わなかったり。 なんだかパスピエの歌詞みたいな終わり方やな。