概要
本記事はEffective Python 第2版の内容を自分なりに短くまとめた記事になります。
※ 執筆中ですが、先行で公開しています。
理解に必要な説明の部分を大きく省略していますし、全てを網羅しているわけでもありません。また、例示のコードや一部内容は私の解釈も含みます。
未読の方は、本書を手に取り読む事を強くお勧めします。
尚、本記事は本書と同様に、Python3(もっというと3.8)を基本とします。
% python3.8
Python 3.8.5 (default, Jul 21 2020, 10:48:26)
[Clang 11.0.3 (clang-1103.0.32.62)] on darwin
Pythonic 思想
The Zen of Python
>>> import this
Pythonで書く時だけならず、プログラマとして一読の価値のある考え方となります。
日本語で解説されている方もおられるので、そちらを見ていただくのも良いと思います。ぐぐってください。
PEP8に従う
コーディングする環境にはPEP8のLinterを入れましょう。
PEP8のスタイルガイド読むとより理解が深まるので一度は読むことをお勧めします。
Linterで全てがチェックされているとも限りません。
以下、Visual Studio Codeでflake8を使ったLintingでチェックされなかった例です。
(2020/08/10時点。Linterも複数あり、それぞれにオプションもあるため、全てにおいてチェックされないことを確認したわけではありません)
# Wrong:
# operators sit far away from their operands
income = (gross_wages +
taxable_interest +
(dividends - qualified_dividends) -
ira_deduction -
student_loan_interest)
# Correct:
# easy to match operators with operands
income = (gross_wages
+ taxable_interest
+ (dividends - qualified_dividends)
- ira_deduction
- student_loan_interest)
# Correct:
if not seq:
if seq:
# Wrong:
if len(seq):
if not len(seq):
※ コンテナやシーケンスが空値の場合、Falseと評価されることを使います。
Python2は止める
Python2はすでにサポートが終了しています。
2to3もしくはsixの利用を検討してください。
文字列のフォーマット
Python3ではいくつかのフォーマット方式がありますが、結論としてf文字列を使ってください。
>>> example = 'pi ='
>>> pi = 3.1415
>>> '%s %.2f' % (example, pi) # フォーマットと変数を対応させる必要がある
'pi = 3.14'
>>> '{} {:.2f}'.format(example, pi) # 同上
'pi = 3.14'
>>> f'{example} {pi:.2f}' # 新しい構文 より簡潔にわかりやすく書ける
'pi = 3.14'
>>> places = 3
>>> f'{example} {pi:.{places}f}' # フォーマット指定子のオプションにも変数が使える
'pi = 3.142'
代入にアンパックを使う
Indexを使うより、見た目がすっきりします。
>>> signals = [('red', 'danger'), ('yellow', 'warning'), ('green', 'safe')]
>>> for signal in signals:
... f'{signal[0]} is {signal[1]}.' # Indexのため、内容が把握しづらい
...
'red is danger.'
'yellow is warning.'
'green is safe.'
>>> for color, state in signals: # アンパックによる代入
... f'{color} is {state}.' # 内容が明確で読みやすい
...
'red is danger.'
'yellow is warning.'
'green is safe.'
Indexが使いたい場合は、enumerateを使う
rangeを使うより、見た目がすっきりします。
>>> favorite_colors = ['red', 'yellow', 'green']
>>> for color in favorite_colors: # indexが不要な場合
... f'favorite color is {color}.'
...
'favorite color is red.'
'favorite color is yellow.'
'favorite color is green.'
>>> for i in range(len(favorite_colors)):
... f'No.{i+1} favorite color is {favorite_colors[i]}.' # 全体的に読みづらい
...
'No.1 favorite color is red.'
'No.2 favorite color is yellow.'
'No.3 favorite color is green.'
>>> for rank, color in enumerate(favorite_colors, 1): # 第二引数で開始する値を指定できる
... f'No.{rank} favorite color is {color}.'
...
'No.1 favorite color is red.'
'No.2 favorite color is yellow.'
'No.3 favorite color is green.'
イテレータを並列に処理するにはzipかzip_longestを使う
rangeやenumerateを使うより、見た目がすっきりします。
並列処理するイテレータの状態により望ましい動作をするzipを使いましょう。
>>> colors = ['red', 'yellow', 'green', 'gold']
>>> states = ['danger', 'warning', 'safe']
>>> for color, state in zip(colors, states): # 短いものに合わせる
... f'{color} is {state}.'
...
'red is danger.'
'yellow is warning.'
'green is safe.'
>>> import itertools
>>> for color, state in itertools.zip_longest(colors, states): # 長いものに合わせる
... f'{color} is {state}.'
...
'red is danger.'
'yellow is warning.'
'green is safe.'
'gold is None.'
for elseを使わない
elseブロックの実行条件が直感的ではないので、ヘルパ関数を用意して記述しましょう。
whileでも同様です。
>>> n = 5
>>> for i in range(2, n):
... surplus = n % i
... if surplus == 0:
... break # else節は実行されない
... else: # break文が実行されなかった場合に実行される
... f'{n} is prime number.'
...
'5 is prime number.'
>>> def is_prime(number):
... '''ヘルパ関数'''
... for i in range(2, number):
... surplus = number % i
... if surplus == 0:
... return False
... return True
...
>>> if is_prime(n): # ヘルパ関数を使うことで処理が明瞭となる
... f'{n} is prime number.'
...
'5 is prime number.'
walrus演算子 :=
python3.8から使えるようになった新しい構文「代入式」に利用する演算子です。
a := b は a walrus(ウォルラス) b と発音します。
代入と比較が同時に行えるようになることで1行減らすことができ、制御構文のスコープ外に制御のための変数(以下の例ではcount)を持ち出さなくて済みます。
>>> example = '9876543210'
>>> count = len(example) # while文だけでcountを使いたいが、外のスコープでの宣言が必要
>>> while count > 1:
... if count == 5:
... f'{example}'
... example = example[1:]
... count = len(example) # 更新用に同じ処理を繰り返し書くケースがある
...
'43210'
>>> example = '9876543210'
>>> while (count := len(example)) > 1: # スコープと行数を抑制できる。演算子の評価順に注意。
... if count == 5:
... f'{example}'
... example = example[1:]
...
'43210'
リストと辞書
シーケンスのスライスとストライド
いくつか抑えておくポイントがあります。
>>> a = [1, 2, 3, 4, 5]
>>> a[:3] # 先頭から利用する場合は、0は省略する
[1, 2, 3]
>>> a[3:] # 末尾から利用する場合も同様
[4, 5]
>>> a[:-1] # 負のIndexも指定可能
[1, 2, 3, 4]
>>> a[:10] # 範囲外のIndexも指定可能
[1, 2, 3, 4, 5]
>>> a[10] # 範囲外に直接のアクセスはNG
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
>>> a[2:4] = ['a', 'b', 'c'] # スライスを使った代入
>>> a # 指定した範囲が代入した値で置き換わり、サイズも変更される
[1, 2, 'a', 'b', 'c', 5]
>>> a[-1:1:-2] # start、end、strideは一度に使わない。負の値が加わると特に複雑になる。
[5, 'b']
>>> c = a[::-2] # メモリや計算が許容できるなら、strideとsliceを分ける
>>> c[:-1]
[5, 'b']
どうしてもstart、end、strideが必要な場合はitertoolsのisliceの利用も検討してください。
catch-all アンパックを使う
Indexを使うより、わかりやすく安全にシーケンスを分割できます。
>>> a = [1, 2, 3, 4, 5]
>>> first, second, *others = a # *をつけることでfirst, second以外をothersでリストとして受け取る
>>> first, second, others
(1, 2, [3, 4, 5])
>>> first, *others, last = a # 順番はどこでも良い
>>> first, others, last
(1, [2, 3, 4], 5)
>>> first, *others, last = a[:2] # catch-all変数の長さは0が許容される
>>> first, others, last
(1, [], 2)
ただし、アスタリスク付きの式は常にリストを返すので、メモリには注意をしてください。
key引数を使ったソート
pythonのlistはsort関数が使えますが、key引数を使うことで複雑な基準でソートが可能です。
(独自に定義されたクラスのオブジェクトもメソッドを定義し、ソートに対応することはできますが、複数の順序付けをサポートする必要があります。)
>>> class Target:
... def __init__(self, text, number):
... self.number = number
... self.text = text
... def __repr__(self):
... return f'{self.text} {self.number}'
...
>>> targets = [
... Target('berry', 4),
... Target('cherry', 2),
... Target('durian', 1),
... Target('apple', 2),
... Target('elderberry', 3)
... ]
>>> repr(targets)
'[berry 4, cherry 2, durian 1, apple 2, elderberry 3]'
>>> targets.sort(key=lambda x:(-x.number, x.text)) # タプルの利用や符号変換ができる
>>> repr(targets)
'[berry 4, elderberry 3, apple 2, cherry 2, durian 1]'
>>> targets.sort(key=lambda x: x.text, reverse=True) # 符号変換ができない場合はreverseで
>>> targets.sort(key=lambda x: x.number) # タプルの場合とは、優先順序が逆転する形になる
>>> repr(targets)
'[durian 1, cherry 2, apple 2, elderberry 3, berry 4]'
dictの挿入順序
T.B.D.