いえーい。最近はPythonの気分なので、その流れで読むぜ。面白かったとこをノートするいつものノリで。

PEP8をまもれ!

Python本はこればっかですね! 先日までSublime Text3にPEP8のチェッカーパッケージを入れていたんだけど、「ほかのひとのコードをネットからコピペしたときペケだらけになって非常に見づらい」「関数間を2行空ける」の2点がイヤでヤメちまった。まあ基本的なところは守っているぜ。

例として記載されていたなかで目新しかったのが下記ふたつ。

  • len(lis) == 0 ではなく not lis を使いましょう。
  • import文は標準モジュール、サードパーティ、自前のモジュールの順番に記載する。

PEP8で気に入ってるやつ

コーディング規約ってのは、知ると目ウロコなものがたくさんある。とくに気に入ってるのが、「一行は79文字以下におさめましょう」ってやつ。これを守るとスクリプトがスッキリして見える。積極的に守っていきたい。

どうしても行が長くなることってあるじゃん? クラス名もメソッド名も長いから書こうとしたらどうしても幅とっちゃうんだよー、とか引数が多いから仕方ないじゃん、とか入れ子が多くなればなるほどインデントが増えるんだからどうしようもないじゃんとか。でもさ、そういうときコーディング規約はこう言ってくるんだよ。「そういうときは設計がおかしい。」この発想は気に入った。そもそも名前は短いほうがいいし、引数はなるたけ少なくするのが基本だし、forとかifの入れ子は少なくするべきだ。

文字列型の bytes と str を知っておく。

文字列型には上記ふたつがあって、それぞれ変換は……

bytes_.decode('UTF8')  # str化
str_.encode('UTF8')    # bytes化

ほんでバイナリファイルの読み書きでは、open関数のオプションに rb とか wb を使うこと。まあそれはいいけどバイナリファイルなんてどんなときにいじるのか俺には想像がつかん。アレだろバイナリって、0と1だけの……。

ディクショナリ.get(キー, None)

キーがなければNoneを返す、というディクショナリのメソッド。うわこれ知らんかった、便利そうだ。

ヘルパー関数を使いましょう。

Pythonは複雑で読みづらい式を1行で書きやすい。そういうときは行を分けて読みやすくして、それをヘルパー関数にまとめましょう。最近高階関数を覚えて大はしゃぎでコードを難読化してる俺には耳が痛いお話です。

スライスを使おう!

  • lis[0:5]lis[:5] は同じ。後者を使いましょう。うへえ、0書いてたわあ。
  • [::2] みっつめにはstride……なん文字ごと、が指定できる。

スライスではstart,end,strideをいっしょに使うな。

lis[2:10:2] はダメ。わかりづらいから。やるなら2行に分けること。

mapやfilterの代わりにリスト内包表記を使おう!

そうか……mapやfilterって、内包表記に置き換えることができるのか。これは目ウロコだ。

a = [0, 1, 2, 3, 4, 5]

# だよんをつける map版
print(list(map(lambda i: f'{i}だよん', a)))

# だよんをつける 内包表記
print([f'{i}だよん' for i in a])

# ↓どちらもこうなる
['0だよん', '1だよん', '2だよん', '3だよん', '4だよん', '5だよん']
# 奇数のみ filter版
print(list(filter(lambda i: i%2 == 1, a)))

# 奇数のみ 内包表記
print([i for i in a if i%2 == 1])

# どちらもこうなる
[1, 3, 5]

……ってことか。この例ではだけど、内包表記を使えば文字数は短くなるし、わざわざlist()を使わなくて済む。逆に結果をイテレータのままにしたい場合はmap、filterの使い所だろうね。

mapとかfilter、内包表記をさらっと書ける自分に成長を感じる! いやあプログラムはじめてから2年たってるもんなあ。

内包表記ではforを重ねることもできるぞ!

rows = [
    [0, 1, 2],
    [3, 4, 5],
]

# 二重forバージョン
_ = []
for row in rows:
    for r in row:
        _.append(r)
print(_)

# 内包表記バージョン
print([r for row in rows for r in row])

# どっちもこうなる
[0, 1, 2, 3, 4, 5]

ただし3つ以上になったらヤメとくこと。読みづらくなるから、ヘルパー関数とか使いましょう。

ところで二重の内包表記の書く順番ってちょっと間違えやすくない? 最初forを二重に内包表記って聞いて、こうかと思った。

[r for r in row for row in rows]

作ろうとしてるリストがバカでかくなっちゃうときは、ジェネレータを使おう!

lis = [i for i in range(10000000000)]

こんなことしたら10000000000個要素が詰まったリストが生成されてメモリが爆発しちゃいそうじゃん? そんなときは

gene = (i for i in range(10000000000))

こうする。中身の取り出しは

next(gene)

これでいっこずつ取り出せる。lis[10]みたいに途中の要素を取り出すことができないのが難点かな? でもでっけえリストを生成しないってのはイイね。

連鎖ジェネレータはすげえ高速。

gene  = (a for a in open('foo.txt', 'r', encoding='UTF8'))
gene2 = (a.strip() for g in gene)

enumerate()の使い所は……リストを回したいが添字も欲しいとき!

for i, value in enumerate(lis, 1):
    print(i, value)

こういうこと。enumerateに第二引数を指定すれば、iがその数からスタートになる。別にlis[1]からスタートってわけじゃない。これ便利ね。

複数のイテレータを回したいならzip()を使うこと。

ああー、これは言われなくても知ってるよー。

# ただしイテレータの長さが違うと、片方が終わったところで終了しちゃう。
# それがイヤなときは itertools.zip_longest() を使うこと。

それはまったく知りませんでしたすみません。

for - else っていう構文がある。

へー! しらなかった!

# が、それは振る舞いが直感的じゃないから使うな。

えー!?

try, except, else, finally の使い分け。

  • try: 例外が起こるかもしれないコード
  • except: 例外が起こったときやること
  • else: tryが成功したときすること。ここをしっかり書いて、tryブロックを最小にしよう!
  • finally: file.close()など後始末を。「たとえelseでreturnが起こってもこのブロックは実行される。」へぇえー!!

Effective Python 感想文一覧