Edited at

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

More than 3 years have passed since last update.

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に続く..?


参考