問題の歴史:
enumerate()関数はPython 2.3で登場し、コレクションを反復処理する際に要素とそのインデックスに同時アクセスするための標準的な方法です。enumerate()が登場する以前は、プログラマーは独自のカウンターを作成するか、range(len(sequence))関数を使用することが多く、これは不便で読みづらいものでした。
問題:
通常のforループは値のみを繰り返します。インデックスにアクセスするには、しばしばrange(len(...))が使用され、これはすべての反復可能なオブジェクト(たとえば、ジェネレーター、文字列、可変長のタプル、フィルタリング時など)に対して機能しません。これによりエラーが発生し、コードが複雑になってしまいます。
解決策:
enumerate()は(インデックス, 要素)のペアを返すため、非標準のコレクションやフィルタリングされたジェネレーターに対しても現在の要素のインデックスを取得することができます。この関数は、カウンターの開始値を設定するための2番目の省略可能な引数を受け取ります。
コード例:
words = ['apple', 'banana', 'cherry'] for idx, word in enumerate(words, 1): print(f"{idx}: {word}") # 出力: # 1: apple # 2: banana # 3: cherry
主な特徴:
なぜ関数内では、range(len(seq))の代わりにenumerateを使うことが多いのですか?
回答: range(len(seq))はインデックスアクセスが可能なシーケンスに対してのみ機能し、反復中に長さが変わることを考慮していません。また、読みづらくなり、ジェネレーターに対しては遅くなるか、まったく機能しません。enumerate()は、任意の反復可能なコレクションに対してインデックス-値のペアに安全にアクセスできます。
コード例:
# ジェネレーターとは動作しません: gen = (x for x in range(5)) for i in range(len(gen)): print(i) # エラー: ジェネレーターには長さがありません
リストの要素を反復処理中に変更するためにenumerateを使用できますか?
回答: はい、インデックスを使って反復する必要があります。そうでなければ、値だけを反復処理すると、オブジェクトのコピーが変更され、元のオブジェクトが変更されません。
コード例:
nums = [1, 2, 3] for idx, val in enumerate(nums): nums[idx] = val * 2 # nums = [2, 4, 6]
イテレーション中に変更されるオブジェクトを渡した場合、enumerateは何を返しますか?
回答: コレクションが反復中に変更された場合(たとえば、要素が削除されるなど)、結果が予測できない動作になる可能性があります。なぜなら、enumerateは内部イテレーターに依存していて、これは壊れる可能性があるためです。したがって、イテレーション中にコレクションを変更することは推奨されません。
range(len(...))を使用する。ネガティブケース
プログラマーがrange(len(list))を使用してリストを反復処理し、要素をその場で削除します。結果 — インデックスがずれ、一部の要素がスキップされます。
利点:
欠点:
ポジティブケース
enumerate()を使用して必要な要素の新しいリストを形成するか、インデックスに基づいて値を変更しますが、ループ中にリストのサイズは変更しません。
利点:
欠点: