sgykfjsm.github.com

Pythonでコーディングするときの小ネタ

The Hacker’s Guide to Pythonという本を読んでいるんだけれども、その中でちょっと感動したテクニックをメモしておく。

まずはコレ。

1
2
3
4
5
6
def get_fruits(basket, fruit):
    """A variation is to use 'if fruit in basket:'"""
    try:
        return basket[fruit]
    except KeyError:
        return set()

dictの中から指定した要素の値を取得する。ただし、指定した要素が無ければ空のsetオブジェクトを返す。っていう、よくあるパターン。これは以下の様に書き換えることができる。

1
2
3
4
def get_fruits(basket, fruit):
    # dict.get(key[, default])はkeyが辞書にあればkeyに対する値を返し、
    # そうでなければdefaultを返す。
    return basket.get(fruit, set())

(コメントを除けば)たった1行になった。しかもこれはdictの組み込み関数なので、ごく自然に使うことができる。

次の例も意外とありがちだと思う。不正な値が検査対象のオブジェクトに含まれていないかを検める例。

1
2
3
4
5
def has_invalid_fields(fields):
    for field in fields:
        if field in ['foo', 'bar']:
            return False
    return True

これは以下の様に書き換えることができる。

1
2
def has_invalid_fields(fields):
    return bool(set(fields) - set(['foo', 'bar']))

単純にそれぞれの集合の差で判定しているだけ。極めて自然だし、わかりやすい。なんでこうゆう簡潔なコードを思いつけないんだ、おれは…。先ほどの例もそうだけど、データ構造の特性やデータ構造に組み込まれた関数をうまく使うことでコードをより自然に、かつ簡潔にすることができる。

次の例はデータ特性を活かして、パフォーマンスを改善させる例。まずは改善前のコード。

1
2
3
4
5
6
7
def add_animal_in_family(species, animal, family):
    if family not in species:
        species[family] = set()
    species[family].add(animal)

species = {}
add_animal_in_family(species, 'cat', 'felidea')

speciesという辞書に新たな要素を追加する場合に、追加する種別(ここではadd_animal_in_familyの3つ目の引数)がキーにあるかどうかを確認して、なければ空の集合を入れておく。そののちに指定された値を辞書に追加している。コレ自体は極めて真っ当なコードなんだけど、もし、このadd_animal_in_familyが100回、1000回と実行されるとしたらどうだろうか?おそらく実行回数が増えるにつれて、if文の条件判定が無駄になることが多くなってくるはずだ。

この問題を改善するのがcollections.defalutdictというデータ構造。こいつをうまいこと使ってあげると、エレガントに対応できる。

1
2
3
4
5
6
7
import collections

def add_animal_in_family(species, animal, family):
    species[family].add(animal)

species = collections.defalutdict(set)
add_animal_in_family(species, 'cat', 'felidea')

見ての通り、collectionsパッケージをimportして、データの初期化にcollections.defaultdictを使うだけで、add_animal_in_familyのif文が不要になってしまった。詳しくはドキュメントを参照するのが一番確実なんだけど、簡単に説明すると、collections.defalutdictに対して存在しない要素へアクセスをしようとすると、defaultdictはKeyErrorを送出する代わりに初期化時に渡されたオブジェクトを使って新たな値を構築して自身である辞書に登録して値を返してくれる。ただし、初期化時に渡す値がNoneの場合はKeyErrorが送出されるので、注意すること。また、ドキュメントに記載されている使用例には初期化時に渡すオブジェクトとしてintを指定しているユニークな例があって、有用。

こんな感じでは、pythonではデータの特性をうまく使うことで小賢しいロジックやアルゴリズムの実装を不要にしてくれて、かつパフォーマンス的にも嬉しい実装をすることができる。もちろん、これはpythonに限った話では無いので、どんな言語であっても扱うデータの特性や組み込み関数についてちゃんと把握しておくことは重要っすな。