Pythonっぽい書き方(イディオム) Part1

Pythonっぽい書き方(イディオム) Part1

Pythonっぽいイディオムについて紹介します. Part1です.

Python3, 2系両方で動くはずです.

for文関連

indexを使いたい時

Bad: range, lenを使う

names = ['hoge', 'suzuki', 'tom']
for i in range(len(names)):
    print(i, names[i])

(0, 'hoge')
(1, 'suzuki')
(2, 'tom')

Good: enumerateを使う

for i, name in enumerate(names):
    print(i, name)

(0, 'hoge')
(1, 'suzuki')
(2, 'tom')

2つのコレクションを扱う時

Bad: indexを使ってアクセスする

names = ['hoge', 'suzuki', 'tom']
ages = [12, 19, 11]

for i in range(min(len(names), len(ages))):
    print(names[i], ages[i])

('hoge', 12)
('suzuki', 19)
('tom', 11)

Good: 組み込み関数zipを使う

for name, age in zip(names, ages):
    print(name, age)

('hoge', 12)
('suzuki', 19)
('tom', 11)

dictionaryでkey, valueを使いたい時

Bad: keyでアクセス, valueを取得する

persons = {'hoge': 12, 'tanaka': 24, 'nakata': 11}
for k in persons:
    print(k, persons[k])

('tanaka', 24)
('nakata', 11)
('hoge', 12)

Good: itemsメソッドを使う

for k, v in persons.items():
    print(k, v)

('tanaka', 24)
('nakata', 11)
('hoge', 12)

値の入れかえ(swap values)をしたい時

Bad: tmp変数を定義する

a = 0
b = 10

tmp = a
a = b
b = tmp

Good: onelineで交換する

a, b = 0, 10

a, b = b, a

リストからdictionaryを作成したい時

1つのリストから作成する場合

names = ['hoge', 'suzuki', 'tom']
d = dict(enumerate(names))

{0: 'hoge', 1: 'suzuki', 2: 'tom'}

2つのリストから作成する場合

names = ['hoge', 'suzuki', 'tom']
ages = [12, 19, 11]
d = dict(zip(names, ages))

{'tom': 11, 'suzuki': 19, 'hoge': 12}

リストをグルーピングしたい時

Bad: ifでいちいちチェックする

names = ['hoge', 'suzuki', 'tom', 'sato', 'seko']
d = {}
for name in names:
    key = name[0]
    if key not in d:
        d[key] = []
    d[key].append(name)

{'h': ['hoge'], 's': ['suzuki', 'sato', 'seko'], 't': ['tom']}

Good: setdefaultを使う

names = ['hoge', 'suzuki', 'tom', 'sato', 'seko']
d = {}
for name in names:
    key = name[0]
    d.setdefault(key, []).append(name)

{'h': ['hoge'], 's': ['suzuki', 'sato', 'seko'], 't': ['tom']}

Good: defaultdictを使う(個人的にはこれが好き)

from collections import defaultdict

names = ['hoge', 'suzuki', 'tom', 'sato', 'seko']
d = defaultdict(list)
for name in names:
    key = name[0]
    d[key].append(name)

defaultdict(<type 'list'>, {'h': ['hoge'], 's': ['suzuki', 'sato', 'seko'], 't': ['tom']})

効率のよいソート

Bad: cmpを用いてソートする

names = ['hoge', 'suzuki', 'tom']
def cmp_function(a, b):
    if len(a) > len(b):
        return 1
    if len(a) < len(b):
        return -1
    return 0
sorted(names, cmp=cmp_function)

['tom', 'hoge', 'suzuki']

Good: keyを用いてソートする

names = ['hoge', 'suzuki', 'tom']
sorted(names, key=len)

['tom', 'hoge', 'suzuki']

ファイルを開く, 閉じる時

Bad: finallyを使う

f = open('build.gradle')
try:
    data = f.read()
finally:
    f.close()

Good: with文を使う

with open('build.gradle') as f:
    data = f.read()

合計を計算する

Bad: forを使う

total = 0
for i in range(10):
    total += i ** 2

Good: リスト内包を使う

total = sum([i ** 2 for i in range(10)])

Good: ジェネレータ式(リスト内包よりパフォーマンス的に優れている)

total = sum([i ** 2 for i in range(10)])

文字列結合をする

Bad: strを + でつないでいく

names = ['hoge', 'suzuki', 'tom', 'sato', 'seko']
s = ''
for name in names:
    s += name

'hogesuzukitomsatoseko'

Good: joinメソッドを使う

names = ['hoge', 'suzuki', 'tom', 'sato', 'seko']
''.join(names)

'hogesuzukitomsatoseko'

デコレータを使う

cacheロジックを外出しする

Bad: メソッド内にcacheのロジックが入りこんでいる

def get_data(filepath, saved={}):
    if filepath in saved:
        return cache[filepath]

    with open(filepaht, 'r') as f:
        saved[filepath] = f.read()
    return saved[filepath]

Good: デコレータを使い, cacheのロジックを外部に切り出す

from functools import wraps

def cache(func):
    saved = {}

    @wraps(func)
    def wrapper(filepath):
        if filepath in saved:
            return saved[filepath]
        result = func(filepath)
        saved[filepath] = result
        return result
    return wrapper


@cache
def get_data(filepath):
    with open(filepath, 'r') as f:
        return f.read()

contextmanagerを使う

例外処理を無視する時

Bad: passで例外処理をスルーする

import os

try:
    os.remove('.tmp')
except OSError:
    pass

Good: contextmanager, with構文を使い, 例外をignoreしていることを強調する

import os
from contextlib import contextmanager

@contextmanager
def ignored(*exc):
    try:
        yield
    except exc:
        pass


with ignored(OSError):
    os.remove('tmp')

まとめ

パート2に続く..?

参考