ちゃぱブログ / エンジニアリング / マネジメント

とあるプロダクトの運用組織のマネジメントしてる人の雑記。主にエンジニアリングやマネジメントのことを書きます。ときおりプチ情報も

(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)