エキスパートPythonプログラミング改訂2版 気になったことメモ
目次

第4章 良い名前を選ぶ

定数: P.154

短くするよりも意図をわかりやすく表現するほうが大切

命名規則と使用例: P.155

デフォルト値や初期値を設定するためにも使用される

  • ユーザーが必要な設定をすべて行わなくてもライブラリが動作する、という設計のほうが扱いやすい

  • 設定として使用する際の良いプラクティスは、パッケージ内の1つのファイルにすべての定数を集めること (Django の settings とか)

  • モジュール内にグループとなる定数がある場合を除いて、定数名の先頭に共通の名前をつける必要はない

    • Python ではモジュール名そのものが接頭辞としての役割を果たすから

  • 組み込みの https://docs.python.org/ja/3/library/enum.html#enum.Enum を使う

辞書型に明示的な名前をつける: P.162

# 例えば dict が名前をキーにしてその人の住所を保持する場合には ``person_address``
# これずっと悩んでた!!
person_address = {
    'Bill': '6565 Monty Road',
    'Pamela': '45 Python street',
}

汎用性の高い名前を避ける: P.163

関数名やクラス名では避けたほうが良いでしょう

  • Manager

  • Object

  • Do, handle または perform

魔法の引数である*args と**kwargs は注意して使用する: P.167

  • 可変引数である *args**kwargs は、関数やメソッドの堅牢性を低下させる

  • メソッドの引数情報を意味のある名前つき引数に固定すべき

  • 別のアプローチとしては、関連する引数をグループ化して実行コンテキストに渡すためのコンテナクラスを作る

    • 内部情報を保持したり、独立して拡張できる

    • コンテナを引数として使用するコードは、そのコンテナの内部構造を気にする必要がなくなる

    def log_request(request):
        print(request.get('HTTP_REFERER', 'No referer'))
    

クラス名: P.169

  • 名前からクラスが何をするのかが十分に理解できるように簡潔で的確な名前にする

  • その型やその特性について伝える接尾辞を使用する

    • SQLEngine

    • MineTypes

    • StringWidget

    • TestCase

  • 基底クラスのクラス名には BaseAbstract

    • BaseCookie

    • AbstractFormatter

  • クラスの属性と一貫性を保つ

    SMTP.smtp_send()  # 主語が重複していて冗長!! ですよね!!
    SMTP.send()  # Good
    

モジュール名とパッケージ名: P.170

  • モジュールやパッケージの名前は、中に含まれる関数やクラスが持っている目的が伝わるような名前にする

  • underscores のない lowercase 形式の短い名前にしましょう

    • sqlite

    • postgres

    • sha1

第2章 構文ベストプラクティス — クラス以外

Pythonic なパターン: P.64

自分自身で真実を深く掘り下げていくことでしか、Pythonの人気のある書き方のどれが本当に正しいかを知ることはできません。

文字列の結合について: P.67

場合に応じて使い分ける

s += substring

文字列の長さに比例した実行コスト

str.join()

結合したい文字列の数が多い、すでに iterable に格納されている

str.format()

事前に文字列数がわかっている

%演算子

事前に文字列数がわかっている

リテラル文字列補完

事前に文字列数がわかっている

Pythonのリスト: P.69

  • Javaとかの LinkedList を使って実装されていると勘違いされがちだけど違うよー

  • CPython のリストは可変長の配列として実装されている。

  • Python のリストは他のオブジェクトへの参照を持った、連続した配列

  • リストの先頭の構造体がこの配列へのポインタと長さを格納している

  • 計算量

    追加、取得

    O(1)

    挿入、削除

    O(n)

    サイズ変更、再割り当てが発生する操作

    O(n)

  • 要素の先頭と末尾への append と pop が O(1) の計算量のリンクリストが必要な場合は、 collections.deque を使用するとよい。

リスト内包表記: P.70

for ループ内で list.appned() するような場合は、リスト内包表記を使用したほうが処理が速くなる。

# これは遅くなる
# リストを操作するコードをループごとにインタープリタ上で処理する必要がある
# append() はリストのメソッドであるため、イテレーションごとに関数ルックアップの追加のコストが必要になる
events = []
for i in range(10):
    if i % 2 == 0:
        events.append(i)
# これは速くなる
# 処理の一部がインタープリタ内部で実行されるようになるので、速くなるとのこと
[i for i in range(10) if i % 2 == 0]

辞書: P.73

  • 辞書内包表記

    squares = {number: number**2 for number in range(100)}
    
  • 辞書内包表記には、リスト内包表記と同じメリットがある。

  • keys() とか、 Python2 と Python3 では返ってくるもの違うので注意

    • Python2: リスト

    • Python3: ビューオブジェクト

  • ビューオブジェクトは辞書の内容のスナップショットではなく、現在の内容を見せるビューを提供する。辞書の内容が変化するとビューはその変化を反映した結果を返す。

    • Python2 と Python3 とで動きが結構違う感じするので、使うとき注意だなぁ・・・

  • 辞書のコピーとイテレーションにおける最悪のケースの計算量の n の数値は、現在格納されている要素数ではなく、辞書が今まで格納してきた最大数とのこと

  • 平均計算量

    要素取得

    O(1)

    要素追加

    O(1)

    要素削除

    O(1)

    コピー

    O(n)

    イテレーション

    O(n)

  • 以前に大量の要素を格納し、その後要素を減らして現在の要素数がすごく少ない辞書でも、イテレーションすると極めて長い処理時間がかかる!

  • 何度もイテレーションされる辞書の場合は、要素を削除する代わりに新しい辞書オブジェクトを作る方が良いこともある。

  • 順序

    • Python3.7より前までは、順序を保持しない

    • Python3.7 以降は、辞書のキーが登録した順序で保持される

辞書の実装詳細: P.74

  • hashable オブジェクトのみがキーとして使える。

  • オブジェクトが hashable であるということは、オブジェクトが生存する期間中ハッシュ値が変わらず、他のオブジェクトと比較が行えるということ。

  • Python の組み込み型のうち、 immutable なものはすべて hashable ですと。

  • 型が hashable な場合には、次の2つのメソッドを持つプロトコルをサポートすべきであると決められています。

    • __hash__

    • __eq__

集合: P.77

  • set()

    • mutable

    • 順序がない有限集合

    • 要素はユニークかつ immutable かつ hashable

    • 空の集合を作るときは set()

    • setリテラルは {1, 2, 3}

  • frozenset()

    • immutable かつ hashable

    • 順序がない有限集合

    • 要素はユニークかつ immutable かつ hashable

集合の実装詳細: P.78

  • CPython実装は辞書に似ている

  • 要素の削除、追加、存在チェックは非常に高速

    平均計算量

    O(1)

    最悪計算量

    O(n)

cllections モジュール: P.78

namedtuple()

タプルのサブクラスを作成するファクトリ関数。名前つきの属性としても要素にアクセスできる。

deque
  • スタックとキューに必要な操作を備えた両端キュー。

  • 先頭と末尾への高速な追加、削除ができる

ChainMap
  • 辞書のようなクラス

  • 複数の辞書をまとめて1つの辞書に見せるビューを作成する。

OrderedDict

要素が追加された順序を保証する辞書のサブクラス

defaultdict

要素が見つからなかったときに、指定された関数を呼び出して初期値を自動作成する辞書のサブクラス

イテレータ: P.79

  • イテレータプロトコルを実装したコンテナオブジェクト

  • イテレータプロトコル

    • __next__(): コンテナの次の要素を返す

    • __iter__(): イテレータ自身を返す

  • シーケンスの要素をすべて取り出し終わると StopIteration 例外が発生する。

  • カスタムイテレータを作成するときは、クラス内に↑のふたつを実装する。

  • itertools: 使ってください、だそうです。

yield文(ジェネレータ): P.81

  • 関数を一時的に停止させ、途中経過の結果を返す。

  • 一時停止中も実行コンテキストが保存されているため、必要であれば止まった場所から再実行できる

  • next() 関数呼び出し、あるいは for ループを使って、イテレータと同じようにジェネレータから新しい値を取得できる

  • ループ処理やシーケンスを返す関数を実装するときには、まずジェネレータの利用を検討すべき

    • 1つずつ要素を返すことで、その要素を使用する他の関数へ渡す場合に全体のパフォーマンスを向上させる。

  • 複数のデータ群を使用するような、データ変換アルゴリズムの効率が向上する。

    • それぞれのデータ群を1つのイテレータとして実装し、高レベル関数の中にそれらを組み込むことで、巨大で読みにくい関数にになるのを防ぐことができる。

    • 一度に1つの結果を算出する複雑な関数よりも、シーケンス上で動作可能な、シンプルな関数をたくさん作る方が良い手法と言える。

  • send(), throw(), close()

    • 外部のクライアントコードからジェネレータ内にデータを送ることができる

    • 動作を変更できる

ジェネレータ式: P.85

iter = (x**2 for x in range(10) if x % 2 == 0)
for el in iter:
    print(el)
  • リスト内包表記に似てる

  • 丸括弧をブラケットの代わりに使用するんだよ

  • yield を使用したシンプルなループや、イテレータのように動作するリスト内包表記は、積極的にジェネレーター式に置き換えるべき

デコレータ: P.85

  • 関数やメソッドのラッピング(受け取った関数を拡張して返す)処理の見た目をわかりやすくする

  • デコレータとして使用できるのは、一般的に、1つの引数(デコレーション対象)を受け取れる、名前付きの callable (呼び出し可能)オブジェクト

  • 返り値として、他の callable オブジェクト(デコレーションした結果)を返す。

  • メソッドと関数に限定されない

  • __call__() メソッドが定義され、 callable とみなせる任意のオブジェクトをデコレータとして使用できる。

  • 実装例はP.87参照のこと

    # 関数として実装する例
    def mydecorator(function):
      def wrapped(*args, **kwargs):
        # 実際の関数を呼び出す前に行う処理
        result = function(*args, **kwargs)
        # 呼び出し後に行う処理
        return result
      # ラッパーをでデコレート済み関数として返す
      return wrapped
    
    # クラスとして実装する例
    class DecoratorAsClass:
      def __init__(self, function):
        self.function = function
    
      def __call__(self, *args, **kwargs):
        # 実際の関数を呼び出す前に行う処理
        result = function(*args, **kwargs)
        # 呼び出し後に行う処理
        return result
    
  • パラメータも受け取れる => 2回ラップが行われる

  • メタ情報を保持するデコレータ => functools.wraps() デコレータを使う

デコレータの活用例: P.90

  • 引数チェック

  • キャッシュ

  • プロキシ

    • プロキシデコレータは関数にタグをつけたり、グローバルな仕組みへ登録したりする

    • たとえば、実行中のユーザーごとにコードへのアクセスを保護するセキュリティレイヤは、呼び出し可能オブジェクトに関連づけられたアクセス許可情報を利用する、集中制御型チェッカーとして実装することができます

  • コンテキストプロバイダ

    • @synchronized とか

コンテキストマネージャー --- with 構文: P.98

  • try..finally

    • エラー発生時のクリーンアップ処理

    • ファイルを閉じる

    • ロックを解除する

    • 一時的にコードにパッチを当てる

    • 特定環境で保護されたコードを実行する

  • with 文はコードブロックの前後で何らかの処理を呼び出すためのシンプルな方法を提供している

  • try..finally 文の代わりに使用できる

クラスとしてコンテキストマネージャーを実装: P.100

  • コンテキストマネージャープロトコルを実装したオブジェクトはコンテキストマネージャーとして使用できる

  • このプロトコルを含むこと

    • __enter__(self)

    • __exit__(self, exc_type, exc_value, traceback)

  • with 構文の実行順序

    • __enter__() メソッドが実行されます。メソッドの返り値は as 節で指定されたターゲットに束縛されます。

    • with 文内のコードブロックが実行されます。

    • __exit__() メソッドが実行されます。

      • finally 節のように後処理を行うために使われるべきです

      • エラーが発生したときには、その例外を上げ直すべきではありません

      • それは呼び出し側の責任です

関数としてコンテキストマネージャーを実装: P.102

  • contextlib モジュール

  • コンテキストマネージャーと一緒に使うためにある

  • contextmanager デコレータが便利らしい

  • 詳しくは P.102 を見てください(あんまりわかってない)

第1章 現在の Python のステータス

  • 選択したプログラミング言語を深く理解することは、エキスパートとしてその言語を利用する上でもっとも大切です。これはどの技術においても常に真です。そして、言語コミュニティ内で一般的に使われているツールやプラクティスを知らずに本当に良いソフトウェアを開発するのは困難です。

  • venv: 新しいバージョンのPythonを使用しているのであれば、virtualenvの代わりにvenvを使う方がよい

  • Awesome-python: 人気のパッケージ情報やフレームワーク情報をまとめたリストを提供しています

  • Python Weekly: とても人気のあるメールマガジンです。興味深いPythonパッケージやリソースについて毎週10本以上の記事を読者に配信しています。

  • プロフェッショナルな開発者の多くは何かしらの種類のデバッガを使うのを好みます。だって。