🐍

Effective Python 読書メモ

2025/02/08に公開

概要

Effective Pythonを読んだ上での要点や、使えそうな知識をメモとして記す

対象本

効率的で堅牢であるだけでなく、読みやすく、保守しやすく、改善しやすいPythonicなコードを書く秘訣を教えます。

https://www.oreilly.co.jp/books/9784873119175/

1章 Pythonic思考

項目2 PEP8スタイルガイドに従う

PEP8 : https://peps.python.org/pep-0008/
正しい構文である限り、コードは自由に記述して良いが、一貫したスタイルに従い記述することでより扱いやすく可読性を向上させる。

  • 空白(スペース)

    • インデントにはタブではなくスペースを使う
    • 各行は長さが79文字以下にする
    • ファイル内では、関数とクラスは、空白2行で分ける
    • クラスでは、メソッドは空白行で分ける
    • 辞書では、キーとコロンの間に空白を置かず、同じ行に値を書く場合には値の前に空白を1つおく
    • 変数代入の前後には、空白を1つ、必ず1つだけ置く
    • タイプヒントでは、変数名の直後にコロンを置き、型情報の前に空白を1つだけ置く
    number: int # 変数名の直後にコロン、型情報の前にスペース
    
    def hoge(): 
        pass
        
    
    class Point: # 上の関数から2行空ける
        coords: Tuple[int, int]
        label: str = '<unknown>'
    
  • 命名に関して

    • 関数、変数、属性は、lowercase_underscoreのように小文字で_を挟む
    • プロテクテッド属性は_leaing_underscoreのように_を先頭につける
    • プライベート属性は__double_underscoreのようにのように_を2つ先頭につける
    • クラス・例外に関しては、CapitalizedWordのように先頭を大文字にする
    • モジュールでの定数は、ALL_CAPSのように全て大文字で_を挟む
    • クラスのインスタンスメソッド(オブジェクトを参照する)は第一引数にselfを使う
    • クラスメソッドは(クラスを参照する)第一引数にclsを使う

    self・clsの役割などについて

    クラスメソッド・スタティックメソッドについて

  • 式と文

    • 式の否定ではなく、内側の項の否定を使う
    • コンテナやシーケンスの長さを使って、空の値の評価を行わない。暗黙にFalseと評価されることを使う
    • 上記と同様に、空の値でない時の評価も暗黙にTrueとなることを利用する。
    # 式の否定を使わない
    if not fruit is apple: # NG
    if fruit is not apple: # OK
    
    # len()を用いて空判定を行わない
    if len(fruit_list) == 0: # NG
    if not fruit_list: # OK
    
    # 空でない時にTrueとなることを利用する
    if len(fruit_list) != 0: # NG
    if fruit_list: # OK
    

項目6 インデックスではなく複数代入アンパックを使う

  • pythonには1つの代入文で複数の値を代入できるアンパック構文がある
  • アンパックを利用することで、コードの意図が明確になり、簡潔に記述できる
    # インデックスを利用する
    def bubble_sort(words):
      for _ in range(len(words)):
          for i in range(1, len(words)):
              if words[i] < words[i-1]:
                  tmp = words[i]
                  words[i] = words[i-1]
                  words[i-1] = tmp
    
    names = ['shine', 'apple', 'carrots', 'great']
      
    bubble_sort(names)
    print(names) # ['apple', 'carrots', 'great', 'shine']
    
    # アンパックを利用する
    def bubble_sort_unpack(words):
      for _ in range(len(words)):
            for i in range(1, len(words)):
                if words[i] < words[i-1]:
                    words[i-1], words[i] = words[i], words[i-1] # アンパックを利用して1行でスワップできる
      
    names = ['shine', 'apple', 'carrots', 'great']
      
    bubble_sort_unpack(names)
    print(names) # ['apple', 'carrots', 'great', 'shine']
    

項目7 rangeではなくenumerateを使う

  • enumerateを利用して、イテレータでループしながら、要素のインデックスを取り出すことができる
  • enumerateの第2引数でカウンタを開始する数を指定できる
  • enumerateは遅延評価ジェネレータである
    • 一度に全てを読み込むわけでなく、必要な時に読み込む
    • forループ毎に値を生成している
    • メモリ効率の上昇や大規模データを扱いやすいという特徴がある
      enumerateのドキュメント
flavor_list = ['vanilla', 'chocolate', 'pecan']
for i in range(len(flavor_list)):
    flavor = flavor_list[i]
    print(f"[{i + 1}]:{flavor} is delicious")
    

for index, flavor in enumerate(flavor_list, 1): # 開始数を1として指定
    print(f"[{index}]:{flavor} is delicious")

イテレータを並列処理するにはzipを使う

  • zipは2つ以上のイテレータを遅延評価ジェネレータでラップする
import itertools

names = ['Ceila', 'Lise', 'Maria']
counts = [len(name) for name in names]

max_count = 0
longest_name = None

# ラップしているイテレータの要素を1つずつ処理する
for name, count in zip(names, counts):
    if count > max_count:
        longest_name = name
        max_count = count
        
names.append('Rosalind') # 新しく名前を追加

# カウント数の更新をしていないので、最短の入力長3回でループが終わる
for name, count in zip(names, counts): 
    print(name)
    
# 全ての要素に対して処理を行いたい場合
for name, count in itertools.zip_longest(names, counts):
    print(f"{name} : {count}") # Rosalind : Noneになる

Discussion