YoutubeでRaymond Hettingerさん(python core developper)がpythonでよくあるミスと正しい書き方を解説する動画を見つけたので、まとめます。
動画では、主にpython2を例に挙げてますが(2013年の動画なので)、ここではできるだけpython3に変換しました。今ではすでに古い書き方になっている場合があるので適宜調べながら使ってください。
Loop
- できるだけiteratorを使う
シンプルなloop
for i in [0, 1, 2, 3, 4, 5]:
print(i**2)
リスト全体をメモリにおいてしまう。
↓
for i in range(6):
print(i**2)
range
がiteratorとして一つずつ生成するので、メモリを無駄に使わない。
python2では
range
はリスト、xrange
がitertor
python3ではrange
がiterator(python2のxrange
の名前が変わった)
リストのloop
colors = ['red', 'green', 'blue', 'yellow']
for i in range(len(colors)):
print(colors[i])
↓
colors = ['red', 'green', 'blue', 'yellow']
for color in colors:
print(color)
下の書き方のほうが速い
逆向きのloop
colors = ['red', 'green', 'blue', 'yellow']
for i in range(len(colors)-1, -1, -1):
print(colors[i])
↓
colors = ['red', 'green', 'blue', 'yellow']
for color in reversed(colors):
print(color)
インデックスも得たい
colors = ['red', 'green', 'blue', 'yellow']
for i in range(len(colors)):
print(i, '--->', colors[i])
↓
colors = ['red', 'green', 'blue', 'yellow']
for i, color in enumerate(colors):
print(i, '--->', color)
2つのリストを同時にloop
names = ['raymond', 'rachel', 'matthew']
colors = ['red', 'green', 'blue', 'yellow']
n = min(len(names), len(colors))
for i in range(n):
print(names[i], '--->'. colors[i]
↓
names = ['raymond', 'rachel', 'matthew']
colors = ['red', 'green', 'blue', 'yellow']
for name, color in zip(names, colors):
print(name, '--->', color)
長さの違うiteratorがzipに入力されると、短い方に揃う。
長い方に揃えるためにはitertools.zip_longest
python2では
zip
はリストを生成(izip
がiterator)、
python3ではzip
がiteratorを生成
カスタムソート
colors = ['red', 'green', 'blue', 'yellow']
def compare_length(c1, c2):
if len(c1) < len(c2):
return -1
elif len(c1) > len(c2):
return 1
else:
return 0
print(sorted(colors, cmp=compare_length)
↓
colors = ['red', 'green', 'blue', 'yellow']
print(sorted(colors, key=len))
keyによるソートで十分か?
十分でない例もあるがほとんどの場合は大丈夫。(SQLはソートを大量にやるが、keyによるソートでやっている)
sentinel valueでループを止める
blocks = []
while True:
block = f.read(32)
if block == '':
break
blocks.append(block)
↓
blocks = []
for block in iter(functool.partial(f.read, 32), ''):
blocks.append(block)
partialが気持ち悪いが、iteratorとして扱えることのメリットが大きい
sentinel value(終了を示す値)はさけたほうが良い
条件によってループから抜ける
def find(seq, target):
found = False
for i, value in enumerate(seq):
if value == target:
found = True
break
if not found:
return -1
return i
フラッグ(found
)を使わなきゃいけない場合の例
↓
def find(seq, target):
for i, value in enumerate(seq):
if value == target:
break
else:
return -1
return i
for内でbreakがなかったらelse文が実行される。elseではなく
nobreak
という名前にすべきだったと後悔しているそう。
Dictionary
Dictionaryのキーをループ
d = {'matthew': 'blue', 'rachel': 'green', 'raymond': 'red'}
for k in d:
print(k)
↑辞書に変更を加えるとおかしなことが起こる
d = {'matthew': 'blue', 'rachel': 'green', 'raymond': 'red'}
for k in d.keys():
if k.startswith('r'):
del d[k]
↑d.keys()
が事前にリストのコピーを作るので、辞書の変更が可能
キーと値をループ
d = {'matthew': 'blue', 'rachel': 'green', 'raymond': 'red'}
for k in d:
print(k, '--->', d[k])
↓
d = {'matthew': 'blue', 'rachel': 'green', 'raymond': 'red'}
for k, v in d.items():
print(k, '--->', v)
リストから辞書を作成
names = ['raymond', 'rachel', 'matthew']
colors = ['red', 'green', 'blue']
d = dict(zip(names, colors))
リスト内の出現回数を数える
colors = ['red', 'green', 'red', 'blue', 'green', 'red']
d = {}
for color in colors:
if color in d:
d[color] = 0
d[color] += 1
↓
d = {}
for color in colors:
d[color] = d.get(color, 0) + 1
↓
d = defaultdict(int)
for color in colors:
d[color] += 1
リストをグルーピングする
names = ['raymond', 'raychel', 'matthew', 'roger', 'betty', 'melisa', 'judith', 'charlie']
d = {}
for name in names:
key = len(name)
if key not in d:
d[key] = []
d[key].append(name)
↓
d = {}
for name in names:
key = len(name)
d.setdefault(key, []).append(name)
↓
d = defaultdict(list)
for name in names:
key = len(name)
d[key].append(name)
get
は辞書に代入はしない。setdefault
は代入する
複数の辞書をくっつける
defaults = {'color': 'red', 'user': 'guest'}
parser = argparse.ArgumentParser()
parser.add_argument('-u', '--user')
parser.add_argument('-c', '--color')
namespace = parser.parse_args([])
command_line_args = {k: v for k, v in vars(namespace).items() if v}
d = defaults.copy()
d.update(os.environ)
d.update(command_line_args)
辞書が大量にコピーされる
↓
d = collections.ChainMap(command_line_args, os.environ, defaults)
コピーせずにもとの辞書をそのまま持つ
コードの可読性向上
関数のkeyword引数
twitter_search('@obama', False, 20, True)
引数の意味がわからない
↓
twitter_search('@obama', retweets=False, numtweets=20, popular=True)
NamedTuple
> doctest.testmod()
(0, 4)
0,4の意味がわからない
↓
> doctest.testmod()
TestResults(failed=0, attempted=4)
TestResults
は
TestResults = namedtuple('TestResults', ['failed', 'attempted'])
で作れる
tupleのunpacking
p = 'Raymond', 'Hettinger', 0x30, 'python@example.com'
fname = p[0]
lname = p[1]
age = p[2]
email = p[3]
↓
fname, lname, age, email = p
複数の状態を同時に更新する
def fibonacci(n):
x = 0
y = 1
for i in range(n):
print(x)
t = y
y = x + y
x = t
実行途中に状態が崩れている瞬間がある。行の順序を間違えやすい
↓
def fibonacci(n):
x, y = 0, 1
for i in range(n):
print(x)
x, y = y, x+y
こっちのほうが人間の思考に近い。
効率化(高速化、省メモリ)
文字列の結合
names = ['raymond', 'raychel', 'matthew', 'roger', 'betty', 'melisa', 'judith', 'charlie']
s = names[0]
for name in names[1:]:
s += ', ' + name
↓
', '.join(names)
リストの更新
names = ['raymond', 'raychel', 'matthew', 'roger', 'betty', 'melisa', 'judith', 'charlie']
del names[0]
names.pop(0)
names.insert(0, 'mark')
遅い
↓
names = deque(['raymond', 'raychel', 'matthew', 'roger', 'betty', 'melisa', 'judith', 'charlie'])
del names[0]
names.popleft()
names.appendleft('mark')
速い
decoratorとcontext manager
- business logicとadministrative logicを分ける
- コードがきれいになる
- 正しく名前をつけないとグッチャになる
- With great power comes with great responsibility
キャッシュ
def web_lookup(url, saved={}):
if url in saved:
return saved[url]
page = urlib.urlopen(url).read()
saved[url] = page
return page
↓
@lru_cache()
def web_lookup(url):
return urllib.urlopen(url).read()
business logicとadministrative logicが分離している
一時的なコンテクスト
oldcontext = getcontext().copy()
getcontext().prec = 50
print(Decimal(355) / Decimal(113))
setcontext(oldcontext)
↓
with localcontext(Context(prec=50)):
print(Decimal(355) / Decimal(113))
ファイルの開閉
f = open('data.txt')
try:
data = f.read()
finally:
f.close()
↓
with open('data.txt') as f:
data = f.read()
スレッドのロック
lock = threading.Lock()
lock.acquire()
try:
print('Critical section 1')
print('Critical section 2')
finally:
lock.release()
↓
lock = threading.Lock()
with lock:
print('Critical section 1')
print('Critical section 2')
エラーの無視
try:
os.remove('somefile.tmp')
except OSError:
pass
↓
with ignored(OSError):
os.remove('somefile.tmp')
標準出力を一時的に置き換え
with open('help.txt', 'w') as f:
oldstdout = sys.stdout
sys.stdout = f
try:
help(pow)
finally:
sys.stdout = oldstdout
↓
with open('help.txt', 'w') as f:
with redirect_stdout(f):
help(pow)
リストの内包記法
result = []
for i in range(10):
s = i**2
result.append(a)
print(sum(result))
↓
print(sum([i**2 for i in range(10)])
↓
print(sum(i**2 for i in range(10)))