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

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



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



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

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


また単純に横に長すぎると読み辛いということかなと思ったりもします。 適切に抽象化してなっていうそういう意味合いかなと思ったりもします。 以下は PEP 7 という Pythonインタープリタ本体を書くときの C 言語のコーディング規約からの引用になります。

  • 4 文字のスペースをインデントとして使い、決してタブを使ってはいけない。
    Use 4-space indents and no tabs at all.

  • 1 行につき 79 文字を超過してはいけない。 もし 1 行 79 文字のルールとこの前の 4 文字スペースのインデントのルールでコードを書くのが厳しいならば あなたのコードは複雑すぎるということだ -- 関数を使うことを考えてください。
    No line should be longer than 79 characters. If this and the previous rule together don't give you enough room to code, your code is too complicated -- consider using subroutines.

PEP 7 - Style Guide for C 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.


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

普通の文章の場合、行末に長短があると見栄えが悪くなります。 しかし、通常のテキストエディタは、横に広い画面のため、行末が長短ガタガタでも改行せざる得ません。 Markdown では、行末が長短ガタガタした改行をシステムに任せてしまいます。

プログラミング言語は、改行が与える意味が大きすぎるため、単純に読めなくなるため、 Python のコードはシステムに改行させません。

インデントが 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. 関数にする

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

  • 4 文字のスペースをインデントとして使い、決してタブを使ってはいけない。
    Use 4-space indents and no tabs at all.

  • 1 行につき 79 文字を超過してはいけない。 もし 1 行 79 文字のルールとこの前の 4 文字スペースのインデントのルールでコードを書くのが厳しいならば あなたのコードは複雑すぎるということだ -- 関数を使うことを考えてください。
    No line should be longer than 79 characters. If this and the previous rule together don't give you enough room to code, your code is too complicated -- consider using subroutines.

PEP 7 - Style Guide for C Code

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. コードそのものの理解のしやすさ。

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

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

長すぎる変数名は炎上を招きやすい

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

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

原因 1. 糖衣構文をどのような場合にも使えるとしてしまった。

主たる要因は for 文を禁止にはできないと言うことかなと思います。 map, filter を使えば読みやすくなるときもありますが、そうではないときもあります。 map, filter を使えば読みやすくなるのは、ごく限られば場合です。

map, filter は ジェネレータ式と、同じ機能です。 GooglePython のコーディング規約には、単純な場合にだけ、ジェネレータ式を使うように示しています。

2.7 Comprehensions & Generator Expressions
Okay to use for simple cases.


ジェネレータ式は、ジェネレータ関数の糖衣構文です。 Python には他にも糖衣構文として三項演算子があります。 三項演算子にも同じような指摘があります。

複雑な式で三項演算子を使うと、途端にわかりにくくなる。
三項演算子?:は悪である。- Qiita

原因 2. 長すぎる変数名を書いてしまった。

副次的な要因としては、関数名が長いことだと思います。長い関数名には、圧迫感があります。 書籍 Readable Code では変数は、初めての人が読んでわかるかどうか?を基準にして決めることを紹介していました。

長い関数名そのものには、全く問題がないと思うのですが。 "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 文はいらないと言ってしまったのと、 ② 長い変数名を使ってしまったことが、炎上の原因かなと思ったりもします。

長い変数名を使うなら、取り上げる例を、もう少し業務ロジック的なもの、 長くならざる得ないものを取り上げていれば、温度感も、もう少し違ったのかなと思ったりもします。

使えるシーンは限られるとは思うのですが、 計算量が悪化しても for 文を分割してかく書き方が、悪くはないのかなと思ったりもします。

# for 文を
builtin_iterbale_types = []
for cls in __builtins__.__dict__.values():
    if isinstance(cls, type) and hasattr(cls, '__iter__'):
        builtin_types.append(cls)
print(*builtin_iterbale_types, sep='\n')
# 分割してしまう
builtin_types\
    = (cls for cls in __builtins__.__dict__.values() if isinstance(cls, type))
builtin_iterbale_types\
    = (cls for cls in builtin_types if hasattr(cls, '__iter__'))
print(*builtin_iterbale_types, sep='\n')
>>> print(*builtin_iterbale_types, sep='\n')
<class 'bytearray'>
<class 'bytes'>
<class 'dict'>
<class 'enumerate'>
<class 'filter'>
<class 'frozenset'>
<class 'list'>
<class 'map'>
<class 'range'>
<class 'reversed'>
<class 'set'>
<class 'str'>
<class 'tuple'>
<class 'zip'>
>>> 


ただ、これだけ温度感が上がってしまうということは、どうしても主語が大きすぎたのかなという気もします。