Pythonのenumerate()を使いこなす

163, 2021-01-19

目次

Pythonのenumerateの使い方

プログラミング言語のPython(パイソン)にはシーケンスやイテレーターを走査するための組み込み関数enumerate()があります。
このenumerate()を使うとfor文でリストなどの添え字と要素を同時に取り出すことが出来ます。

Pythonのfor文は独特な記法になっているため添え字の扱いに工夫が必要ですが、enumerate()を使えばストレスなく添え字を取り出すことが可能です。

この記事ではenumerate()の使い方を解説します。
具体的には↓を見ていきます。

  • enumerate()の概要

  • enumerate()の構造

  • enumerate()の使い方

  • 独自enumerate()の実装

  • ソースコードの解析

enumerate()の概要

enumerate()はPythonの組み込み関数として定義されています。
公式のドキュメントは↓です。

enumerate()はリストやタプルなどをfor文で回す時に、要素の添え字が欲しくなった場合に使う組み込み関数です。
for文でリストやタプルなどを走査するときに、このenumerate()を使えば簡単に走査中の要素の添え字を取り出すことが出来ます。

Pythonを起動してenumerateprint()で出力すると↓のように表示されます。

>>> print(enumerate)
<class 'enumerate'>

enumerate()は「組み込み関数」というカテゴリに分類されていますが、その実体は↑を見てもわかるようにクラスです。
enumerateクラスのコンストラクタにリストやタプルなどを渡すと、enumerate()はオブジェクトを生成しその状態を初期化します。
そしてfor文でオブジェクトとして参照されたときに設定されているリストやタプルなどを走査するという仕組みです。

これらのコードはPythonではなく実装レベルの言語で書かれています。
たとえばPythonの実装がCPythonであればenumerate()の処理はC言語で書かれています。
このためクラスとは言ってもその動作はPythonのクラスに比べて非常に早くなっています。これが「組み込み」に分類されるゆえんかなと推測できます。

enumerate()の構造

enumerate()は↓のような構造になっています。

enumerate(iterable, start=0)

enumerate()は引数を最大で2つ取り、1つの返り値を返します。

iterable(第1引数)

enumerate()の第1引数には走査対象のオブジェクトを渡します。
これはリストやタプルなどです。
シーケンスやiterator, あるいは__iter__を実装しているオブジェクトを渡すことが出来ます。

start(第2引数)

enumerate()の第2引数には添え字の開始点を渡します。
たとえばこのstartの値が3であれば、for文で参照できる添え字は3から始まります。

enumerate()の使い方

enumerate()は↓のように使います。

lis = ['cat', 'dog', 'bird']

for i, v in enumerate(lis):
    print(i, v)

↑のコードではlisというリストをenumerate()で参照しています。
↑のコードの実行結果は↓のようになります。

0 cat
1 dog
2 bird

↑のようにenumerate()を使うと添え字と要素を同時に取り出すことが可能です。

開始点の指定

添え字の開始点(start)を指定する場合は↓のようにします。

lis = ['cat', 'dog', 'bird']

for i, v in enumerate(lis, 3):
    print(i, v)

↑のコードの出力は↓のようになります。

3 cat
4 dog
5 bird

独自オブジェクトを走査する

クラスで独自オブジェクトを作り、そのオブジェクトをenumerate()で走査させたい場合は↓のような実装が考えられます。

class MyList:
    def __iter__(self):
        yield 'cat'
        yield 'dog'
        yield 'bird'

mylis = MyList()

for i, v in enumerate(mylis):
    print(i, v)

↑のようにクラスの__iter__()メソッドを実装し、ジェネレーターやiter()を返すようにするとそのオブジェクトはenumerate()で参照できるようになります。
↑の場合、__iter__()yieldを実行し、ジェネレーターを生成しています。
↑のコードの出力は↓のようになります。

0 cat
1 dog
2 bird

独自enumerate()の実装

enumerate()を自分で作りたい場合は↓のような実装例が考えられます。

class myenumerate:
    def __init__(self, sequence, start=0):
        self.sequence = sequence
        self.start = start

    def __iter__(self):
        n = self.start
        for elem in self.sequence:
            yield n, elem
            n += 1

lis = ['cat', 'dog', 'bird']

for i, el in myenumerate(lis, 3):
    print(i, el)

↑のコードのmyenumerate()は組み込みのenumerate()と同じ動作をします。
しかし動作については組み込みのほうに比べて非常に遅いです。
これは組み込みのenumerate()がC言語で書かれているのに対して、↑のmyenumerate()はPythonで書かれているためです。
実用性があるかどうかはわかりませんが、使用する場合は速度にだけ注意が必要と言えます。

ソースコードの解析

enumerate()はCPythonによる実装の場合、enum_new()関数が最初に呼ばれます。
これはObjects/clinic/enumobject.c.hで定義されています。

enum_new()では引数を取り出して、最終的にenum_new_impl()を呼び出しています。
enum_new_impl()Objects/enumobject.cで定義されています。

enumerate()で次の要素を生成するときに呼ばれるのはenum_next()です。
この関数では最終的に添え字とオブジェクトを生成し、それをタプルに包んで返しています。

おわりに

今回はenumerate()の使い方を見て見てみました。
enumerate()はリストなどの要素を添え字と一緒に取り出したい時に便利な組み込み関数です。
添え字が必要になったら使ってみてください。