連載
» 2019年06月11日 05時00分 公開

Python入門:[Python入門]リストと繰り返し処理 (2/3)

[かわさきしんじ,DeepInsider編集部]

enumerate関数

 Pythonには組み込みのenumerate関数がある。これは反復可能オブジェクトとそのインデックスを利用したい場合に便利に使える。

enumerate関数

enumerate(iterable, start=0)


 反復可能オブジェクトを受け取り、そのインデックスと要素を組にしたタプルを要素とするイテレータ(連続するデータを表すオブジェクト)を戻り値とする。なお、イテレータについては後述する。

パラメーター 説明
iterable 要素とそのインデックスの両者を取り扱いたい反復可能オブジェクト
start インデックスの初期値。省略可能。省略時は「0」が指定されたものと見なされる
enumerate関数のパラメーター


 enumerate関数は、リストに含まれている要素にインデックスを(デフォルトでは)0から割り振り、「(インデックス, 要素)」というタプルにまとめて取り出せるようにしたイテレータを返すものだ(タプルについては次回に取り上げる)。for文の「for ループ変数 in 反復可能オブジェクト」の「反復可能オブジェクト」部分にenumerate関数呼び出しを置くことで、「反復可能オブジェクト」部分に「range(len(languages))」のような記述をする必要がなくなる。このとき、ループ変数には「(インデックス, 要素)」という「タプル」が渡される。よって、for文の1行目は次のような形になる。

for (インデックス, 要素) in enumerate(リスト):
    for文の本体

enumerate関数が返すイテレータを用いてインデックスと要素を受け取るようにしたfor文

 かっこ「()」はそれが「1つのタプル」であることを意味する。つまり、2つの値が1つのタプルとしてまとめられ、それがenumerate関数の戻り値であるイテレータから戻されるということだ(関数の戻り値は1つだけなので、タプルを利用して複数の値を「1つのタプル」として返すようになっている)。ここでは「インデックス」には「要素」のインデックスを受け取る変数を、「要素」にはその要素を受け取る変数を記述する。「リスト」には対象のリストを書く。よって、ここではfor文は次のようになる。

for (index, language) in enumerate(languages):
    for文の本体

ループ変数でリストlanguagesのインデックスと要素を受け取る

 詳しくは次回に説明するが、タプルを囲むかっこ「()」は省略できるので、これは次のように書いてもよい。

for index, language in enumerate(languages):

タプルを囲むかっこ「()」を省略した記述

 先ほどのコード全体は次のようになる。

languages = ['Python', 'Ruby', 'PHP']

print(languages)

for index, language in enumerate(languages):
    languages[index] = f'{index}: {language}'

print(languages)

for文の中で要素を変更したいリスト

 リストの要素自体を変更したいので、for文の本体の代入文では、左辺に「languages[index]」と書く必要があることには注意しよう(単純にインデックスと要素を読み出したいだけなら、その必要はない)。右辺は「f'{index}: {languages[index]}'」という式だったのが、要素の値を読み出すのにインデックス指定を行わずに「f'{index}: {language}'」と書けるようになった。range関数とlen関数の組み合わせよりも(若干ではあるが)スッキリとコードになったはずだ。

 実行結果を以下に示す。

実行結果 実行結果

zip関数

 zip関数は2つ以上のリスト(などの反復可能オブジェクト)の要素をまとめたイテレータを作成する。これは複数のリストが関連性を持ち、関連する要素をインデックスで串刺しにできるような場合に便利に使える。

zip関数

zip(*iterable)



 iterableに指定された複数の反復可能オブジェクトの要素をまとめたイテレータを戻り値とする。

パラメーター 説明
*iterable 複数の反復可能オブジェクトをカンマ区切りで指定する。それらのインデックス0の要素をまとめたタプル、インデックス1の要素をまとめたタプル、……、を返すイテレータが戻り値となる
zip関数のパラメーター


 例えば、上で見た名前を格納しているリストと、その人の体重を格納しているリストがあったとする。同じインデックス位置にある名前と体重は「その人の名前と体重」の組を表すものとしよう。

関連するデータを格納する2つのリスト 関連するデータを格納する2つのリスト

 この例では、名前のリストと体重のリストがあり、そのインデックス0の位置にあるのは「一色さんのデータ」で、インデックス1の位置にあるのは「かわさきさんのデータ」で、インデックス2の位置にあるのは「遠藤さんのデータ」のようになっている。

 プログラムコードにすると次のようになる。

namelist = ['一色', 'かわさき', '遠藤', '島田', '小川']
weightlist = [65, 80, 70, 75, 72]

名前と体重を格納するリスト

 これらのリストを基に「一色さんの体重は65kgです」のようなメッセージを表示するには、先ほど紹介したenumerate関数を使って次のように書ける。

for index, name in enumerate(namelist):
    print(f'{name}さんの体重は{weightlist[index]}kgです')

2つのリストを基にメッセージを表示するコード

 このコードでは、リストnamelistからインデックスを作成して、それをもう1つのリストweightlistの要素にアクセスするためにも使っている。これでももちろん構わないのだが、zip関数を使うと、2つのリストを組み合わせて使うことがより明確になると共に、インデックスなしでそれぞれのリストの要素に直接アクセスできるようになる。

for name, weight in zip(namelist, weightlist):
    print(f'{name}さんの体重は{weight}kgです')

zip関数の使用例

 実行結果を以下に示す。

実行結果 実行結果

 zip関数を使う際には、引数として渡すリストの要素数に注意しよう。全てのリストの中で最も短いリストの要素数に合わせて、戻り値のイテレータは作成される。以下に例を示す。

shortlist = list(range(4))      # [0, 1, 2, 3]
longlist = list(range(10, 20))  # [10, 11, 12, ..., 19]
for x, y in zip(shortlist, longlist):
    print(x, y)

zip関数に渡すリストの長さが異なるコード

 このコードでは、リストshortlistは要素を4つ持つリストで、リストlonglistは要素を10個持つリストとなっている。これらをzip関数でひとまとめにして、各要素をループ変数xとyに受け取り、print関数で画面に表示している。実行すると、次のようにリストshortlistの要素数に合わせて、リストlonglistの残りの要素は反復されない。

実行結果 実行結果

 なお、zip関数には3つ以上の反復可能オブジェクトを渡すことも可能だ。一方、反復可能オブジェクトを1つだけ渡した場合には、その要素だけからなるタプルを返すイテレータが作成される。

イテレータと反復可能オブジェクト

 ここまでに何度か「イテレータ」という言葉が出てきたので、ここで簡単に説明をしておこう。

 Python公式サイトの用語集では「イテレータ」を「データの流れを表現するオブジェクト」と述べている。具体的には「それが持つ__next__メソッドを呼び出したり、next関数にそれを引数として渡したりすると、次の値を取り出せる」ものが「イテレータ」である。

 一方、反復可能オブジェクト(イテラブル)については、同じくPython公式サイトの用語集では「要素を一度に1つずつ返せるオブジェクト」としている。その代表的なものはリストや文字列だ。

 ここまでに何度も見てきたように、リストはfor文でその要素を順次取り出せる。一方、for文の「for ループ変数 in 反復可能オブジェクト」という構文の「反復可能オブジェクト」部分にはイテレータを置くことも可能だ。このことから「イテレータは反復可能オブジェクトの一種」と考えてもよい。

 リストとは要素を格納するオブジェクトであり、それらの要素全てがコンピュータのメモリに実在する。これに対して、イテレータは「データの流れ(連続するデータ)を表現するもの」であり、データの流れを表現できるのなら、コンピュータのメモリ中にその全要素が実在しなくても構わない。

 例えば、「1、2、……、10」のような単純な整数列であれば、イテレータ内部でカウンターを1つ持ち、イテレータに「次の値は?」と問い合わせをするたびにそれをカウントアップして、値を返すようにすれば、整数列をメモリに取っておく必要はない。イテレータで済む場面であれば、それを使うことでメモリ使用量の節約につながる。

リストとイテレータ リストとイテレータ

 では、for文でイテレータとリストを問題なく利用できるのはなぜかというと、リストにはその要素を取り出すためのイテレータを得る機能があるからだ。これを試すには組み込みのiter関数が使える。この関数に反復可能オブジェクトを返すと、その要素を反復的に返すイテレータが取り出せる。以下に例を示す。

intlist = [0, 1, 2]
iterator = iter(intlist)

print(iterator.__next__())
print(next(iterator))
print(iterator.__next__())
print(next(iterator))

リストからイテレータを取り出して、next関数と__next__メソッドで各要素を取り出す

 このコードでは、要素を3つ持つリストを作成して、それをiter関数に渡して、リストの要素を反復するためのイテレータを取り出している。その後、next関数や__next__メソッドでイテレータから連続的に値を取り出している。これを実行すると次のようになる。

実行結果 実行結果

 要素が3つしかないのに、next関数と__next__メソッドを4回呼び出しているので、エラーが発生しているのに注意しよう。イテレータで反復する要素が尽きると、next関数や__next__メソッドの呼び出しはエラー(StopIteration例外)を発生する。イテレータは「要素の反復取り出し」を全要素について一度は行ってくれるが、全要素を取り出した後は、値を取り出そうとしても常にエラーとなる。これに対して、リスト(などの反復可能オブジェクト)は常に各要素にアクセス可能である点にも注意しよう(for文でリストの要素を全て取り出した後でも、リストの要素にはアクセス可能)。

 イテレータは次の2つのメソッドを持つ。

  • __iter__メソッド:イテレータ自体を戻り値とする
  • __next__メソッド:次の要素を戻り値とする

 そして、反復可能オブジェクトは次のメソッドを持つ。

  • __iter__メソッド:そのオブジェクトが持つ要素を反復するイテレータを戻り値とする

 for文に反復可能オブジェクトやイテレータを渡すと、__iter__メソッドを使って「イテレータ」が取り出されて、要素が尽きるまで繰り返し処理を行うようになっている。そのため、for文にはどちらのオブジェクトを渡しても統一的に扱えるようになっているというわけだ。

 なお、リストを作成する組み込みのlist関数にはイテレータも渡せる。そのため「list(イテレータ)」とすれば、イテレータからリストを作成できる。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。