PEP 8 と矛盾すると感じる3つの構文


自分が思う Python の仲間はずれがいます。どういうことかというとワンライナーということです。


PEP 8

PEP 8 は1行で書くことを嫌っています。

そのほかの推奨事項 - PEP 8
Other Recommendations - PEP 8

複合文(複数行を1行で書くこと)は、一般に推奨されません。
Compound statements (multiple statements on the same line) are generally discouraged.

# Yes:
if foo == 'blah':
    do_blah_thing()
do_one()
do_two()
do_three()

# Rather not:
if foo == 'blah': do_blah_thing()
do_one(); do_two(); do_three()

時として短い本文を添えて if/for/while を1行で書いても良いですが 複数の節からなる文に対しては決してしないでください。 また、そのような長い行を折り返さないでください(自分で改行させてください)
While sometimes it's okay to put an if/for/while with a small body on the same line, never do this for multi-clause statements. Also avoid folding such long lines!

# Rather not:
if foo == 'blah': do_blah_thing()
for x in lst: total += x
while t < 10: t = delay()


# Definitely not:
if foo == 'blah': do_blah_thing()
else: do_non_blah_thing()

try: something()
finally: cleanup()

do_one(); do_two(); do_three(long, argument,
                             list, like, this)

if foo == 'blah': one(); two(); three()


ちなみに標準モジュールの中で bisect の中で PEP 8 絶対にやるな Definitely not といわれていた書き方をされている箇所があります。

def bisect_left(a, x, lo=0, hi=None):
    ...
    while lo < hi:
        mid = (lo+hi)//2
        if a[mid] < x: lo = mid+1
        else: hi = mid

cpython/Lib/bisect.py

reduce

reduce は組み込み関数から除外されました。その時 Guido は reduce なんか使うくらいなら for 文使った方がいいと言っています。

import functools
lst = [1, 2, 3, 4, 5]

# 1. 明示的に累積ループで書く
def product(s):
    p = 1
    for e in s:
        p = p * e
    return p
product(lst)  # 120

# 2. reduce で書く
functools.reduce(lambda x, y: x * y, lst)  # 120

したがって私の中では reduce 関数が適用できるのは、 結合演算子にごく限定される(訳注: 結合演算子 ... 例えば +, -, *, / などの四則演算子。 それ以外の事例では明示的に累積ループを書いた方がよい。

リスト内包表記にだって違和感を感じる

なので PEP 8 を知ってからリスト内包表記を知った時は、すごく違和感を感じました。

煽りっぽいタイトルだが、この記事は真剣である。リスト内包表記には python 哲学の本質に関わる問題が潜んでいる。 python 使いはリスト内包表記を好む。 他の言語の使用者なら「for 文で書きゃ良いのに」と思うような処理を、リスト内包表記で書くことを好む。 それはなぜなのか。
なぜ python 使いは単純な for 文より醜悪なリスト内包表記を好むのか
ttps://hayataka2049.hatenablog.jp/entry/2018/01/19/140958


約数を求めるコード。リスト内包表記と条件式を使っています。

def divisorize(fct):
    b, e = fct.pop()  # base, exponent
    pre_div = divisorize(fct) if fct else [[]]
    suf_div = [[(b, k)] for k in range(e + 1)]
    return [pre + suf for pre in pre_div for suf in suf_div]

Python で素因数分解と約数を求める。

思ったこと

ワンライナーは罪深い。

リスト内包表記がもめたかどうかは知らないのですが。 三項演算子を採用するかどうか議論した時はかなり揉めたそうです。 代入文に至っては採用にあたって Guido が辞めてしまいました。

実用性が重視されるようになった

最初はすごく窮屈な言語だったような気がします。書き方がすごく限定されているからです。 それが Python の良さであり、自分が Python を使っていた理由でもあったのですが。 おそらくこれは Guido が教育言語 ABC に携わっていたことに影響されているのかなと思ったりもします。

でも色々作っているうちに、いろんな機能が欲しくなって付け足していかれたのかなと。 ABC の影響よりも、現場での開発の影響が大きくなったのかなと。 個人的な意見を言えば窮屈なままであって欲しかったかなと。

ワンライナーは書いた側は気持ちいいけど、読む側は辛いんじゃないかなとも。 でも慣れれば読みやすいかなと...

糖衣構文は、ごく限定された状況では良いということかな。 リスト内包表記にしても三項演算子にしても。

うまく言葉にできない... きっと頻度によるのかなと for 文にしたってイテレータと while 文を組み合わせたものの糖衣構文でしかないし...

糖衣構文は使用できるものが限定される

糖衣構文は、完全な解や代替策を提示してくれるものではなく、部分最適な解を与えてくれるものなのかなとも思います。 raccy 先生の文章は、柔らかくて、個人的にすごく好き。

複雑になるとわかりにくい。
三項演算子?:は悪である。 - Qiita

頻度と読みやすさ

新たに糖衣構文を採用するときは、その使用する頻度と、慣れた時の読みやすさが重要かなと思いました。

Python三項演算子なんて、初めてみた時、物凄い拒否反応があったのに、 悔しいですが、なんだかんだで使ってしまいます。

冷静に考えてみると三項演算子以上に普通の if 文が嫌いだからです。 プログラムの読みやすさと if 文の多さは密接に関わっています。
循環的複雑度 - Wikipedia

if 文について考えたくないのです。 もし、こうだったらこうしようとか、もしああだったら... みたいなコードを読むのも書くのもしたくないのです。

early return だって嫌です。これを避けられる方法がないか、よく、うすらぼんやり考えたりします。

最初は Kotlin の三項演算子の方が自然な書き方にも思えましたが。なぜか Python三項演算子の方がしっくりくるのは、"もし" について考えなくて済むからです。最初に読むときは if から後ろを読み飛ばすことができます。

if (c) a else b  # Kotlin
a if c else b  # Python

まとめ

新規の構文の導入にあたっては、頻度と慣れた時の読みやすさを軸に考えればいいかなと思いました。 また、糖衣構文は完全な解を提供してくれるものではなく、ごく部分的な解を提供してくれていることを念頭に置いておく必要があるように思います。