(Python)range、enumerate、zip、itertoolsを使ったループ例
はじめに
「Effective Python」を読む中での自分用のメモ。「range」「enumerate」「zip」あたりの挙動について整理する。 本でいうと項目7、8あたりの内容。
range
https://docs.python.org/ja/3/library/stdtypes.html#range
ただ繰り返すときによく利用する。例えば10回for文を回す、みたいなときによく使う。
for i in range(10): print(i)
みたいな。 とりあえず回数分処理させたいだけならこれでOK。
enumerate
https://docs.python.org/ja/3/library/functions.html#enumerate
何回目のループかをループ内で把握しておきたい場合に利用する。enumerateの展開結果はジェネレータである。
hoges = ["a","b","c"] gen = enumerate(hoges) print(next(gen)) # (0, 'a') print(next(gen)) # (1, 'b') print(next(gen)) # (2, 'c')
これをアンパックすると、以下のような使い方ができる。
hoges = ["a","b","c"] for i, hoge in enumerate(hoges, 1): print(f"{i}番目の文字は{hoge}です") # ※以下の文字が出力される # 1番目の文字はaです # 2番目の文字はbです # 3番目の文字はcです
zip
https://docs.python.org/ja/3/library/functions.html#zip
ふたつのリストを並列に処理させるときに利用する。
days = ["9/16", "9/17", "9/18"] weathers = ["晴れ", "曇り", "快晴"] for day, weather in zip(days, weathers): print(f"{day}は{weather}です") # 9/16は晴れです # 9/17は曇りです # 9/18は快晴です
こんな感じ。それぞれ別のcsvファイルからデータを取ってきてがっちゃんこしたいときとかに使えそう。 注意点としては、zipで取り扱うリストは密な関連があること。どちらかが変わると影響を受ける。 片方が短い場合には、短いほうに合わせて出力されるので、注意が必要。上に書いた複数のcsvをがっちゃんこするようなケースで、片方がデータを1行ロストしていたりすると、もう片方も消える。そして気づきにくい・・・ そんなときのため?には、itertools.zip_longestを利用する。
itertools.zip_longest
https://docs.python.org/ja/3/library/itertools.html#itertools.zip_longest
zipの項目で記載したようなサイズが異なるリストをくっつけるときに利用する。 こんな感じ。比較のためにzipの挙動も再掲。 fillvalueを入れておくと、それが出力される(これは天気が少ないのを見越したfillvalueになってしまったが)。 defaultは "None" と表示される。
days = ["9/16", "9/17", "9/18", "9/19"] weathers = ["晴れ", "曇り", "快晴"] for day, weather in zip(days, weathers): print(f"{day}は{weather}です") # 9/16は晴れです # 9/17は曇りです # 9/18は快晴です #--------------------------# import itertools days = ["9/16", "9/17", "9/18", "9/19"] weathers = ["晴れ", "曇り", "快晴"] for day, weather in itertools.zip_longest(days, weathers, fillvalue="天気がわからない"): print(f"{day}は{weather}です") # 9/16は晴れです # 9/17は曇りです # 9/18は快晴です # 9/19は天気がわからないです
おまけ
上記を踏まえて、以前に書いたコードがenumerateで置き換えられるなぁというのを反省例として書いておく。 実際はDjangoのshell経由でFactoryBoy使ってDBにデータ登録するものだったけど、少し簡略化して記載。
class Word: def __init__(self, word_id:int, word:str): self.word_id = word_id self.word = word class IdNumber: def __init__(self): self.number = 0 def get_number(self): self.number += 1 return self.number # Wordの作成 word_counter = IdNumber() Word(word_id=word_counter.get_number(),word='Django') Word(word_id=word_counter.get_number(),word='Git') Word(word_id=word_counter.get_number(),word='Python') Word(word_id=word_counter.get_number(),word='Go') Word(word_id=word_counter.get_number(),word='Oralce')
わざわざカウンターなんて作っていたのだけど、こんな簡単に書き換えれる。データを足したいときにも、わざわざ1行足さずにリストに追加するだけで済む。うーん、無知を恥じる。
class Word: def __init__(self, word_id:int, word:str): self.word_id = word_id self.word = word wordlist = ['Django', 'Git', 'Python', 'Go', 'Oracle'] for i, word in enumerate(wordlist, 1): Word(word_id = i, word = word)