リストはfor文を使用した繰り返し処理とよく組み合わせて使われる。そこで便利に使える関数や「イテレータ」という概念などを取り上げる。
* 本稿は2019年6月11日に公開された記事を、Python 3.12.0で動作確認したものです(確認日:2023年10月16日)。
前回はリストを操作するためのメソッドや関数などを取り上げた。今回は、リストに格納されている要素を効率よく利用する方法を幾つか示していこう。
これまでの連載で何度か触れたが、リストは繰り返し処理(for文)と相性がよいデータだ。例えば、第10回「for文による繰り返し処理」の「for文とリスト」ではリストに格納されている要素を一つ一つ画面に表示するのに、以下のようなコードを書いた。
names = ['一色', 'かわさき', '遠藤']
for name in names:
print(name)
このコードに示したように、for文ではリスト(などの反復可能オブジェクト)の各要素をループ変数(上の例では「name」)に順次代入していき、for文の本体を一度実行するたびに代入されたその値を使用できる。ここでは、以下のような整数値を格納するリストがあるとして、その全要素の和を計算するコードを考えてみよう。
intlist = [44, 99, 78, 36, 86, 100, 89, 48, 50, 58]
このリスト(intlist)は要素数が10、その値はバラバラだ。要素の和を計算するプログラムは、繰り返し処理を使わなければ、次のようになるだろう。
result = intlist[0] + intlist[1] + intlist[2] + intlist[3] + intlist[4] +\
intlist[5] + intlist[6] + intlist[7] + intlist[8] + intlist[9]
print(result)
リストを代入した変数の名前が「intlist」と長めであることもあって、これを書くのはかなり面倒くさい。要素が10個なら面倒くさいと思っても書けないことはないが、100個あったとしたらどうだろう。
ちなみに、上に示したように行が長くなるときには「バックスラッシュ」(\)記号を使って途中で改行してもよい。これを「明示的な行継続」と呼ぶ。物理的には2行あるように見えるが(物理行)、論理上はこれらの行は1行として見なされる(論理行)。バックスラッシュで1行の論理行を2行以上の物理行に分けた場合、それらの行に関してはインデント幅に気を使う必要はない。そのため、上ではコードが見やすくなるように「intlist」が同じ位置から始まるように調整している。
そこで、これをfor文を使って、各要素を繰り返し処理の中で加算していくことで、総和を求めるようにしたのが以下のコードだ。変数resultの初期値は0で、ここに繰り返し処理のたびに要素の値を加算していくことで、最終的にはリストの要素の総和が得られる。
result = 0
for number in intlist:
result = result + number
print(result)
このコードのよいところは「リストの要素数が変わったときでも、for文でそれを処理する部分は同じコードで済む」ことだ。最初のコードでは「intlist[0] + intlist[1] + ……」のようにリストの要素をプログラマーがいちいち列挙していたが、そんなことはPythonにまかせてしまうことでどんどん楽をするように、また「自分が楽をするにはPythonにどんなコードを実行させればよいか」を考えていくようにしよう(最初は面倒くさいコードを書いても構わない。そのコードを徐々に改変しながら、最終的に自分が楽になるようなコードが書き上げられればよい)。
以下に実行結果を示す。
もっと簡単な方法もある。上の例ではfor文を使って総和を求めたが、実はPythonにはリスト(などの反復可能オブジェクト)の総和を求めるsum関数が用意されている。
sum(iterable[, start])
引数に渡した反復可能オブジェクトの要素の総和を求める。パラメーターstartの値を明示的に指定した場合は、その値を初期値として「startの値+総和」が戻り値となる。
パラメーター | 説明 |
---|---|
iterable | 要素の総和を求めたい反復可能オブジェクト |
start | 総和にstartの値を合計したものが戻り値となる(省略可能)。省略した場合は0を指定したものと見なされる |
sum関数のパラメーター |
この関数を使えば、上のリストはもっと簡単になる。
result = sum(intlist)
print(result)
上では「楽をするにはうんぬん」と書いたが、「楽をする」にはまずやりたいことが何らかの機能(Pythonが標準で提供する関数など)として既に提供されているかどうかを調べるところから始めるとよい。例えばPython公式サイトのドキュメント「組み込み関数」には、Pythonが標準で提供している関数が一覧されている。
次にfor文の中からリストの要素を変更する方法について見てみよう。ここでは、以下のリストを作成することにする。
languages = ['Python', 'Ruby', 'PHP']
前回にも登場したプログラミング言語の名前を要素とするリストだ。この要素をfor文の中で「0: Python」「1: Ruby」「2: PHP」のようにインデックス(とコロン)を付けるようにしたいとしよう。これまでに説明してきたことから想像すると、次のようなコードが考えられる。
languages = ['Python', 'Ruby', 'PHP']
print(languages)
counter = 0
for language in languages:
language = f'{counter}: {language}'
counter += 1
print(languages)
ここでは変数counterを使ってインデックスを管理していること以外は特に述べるようなこともない。だが、実行すると、意外な結果となる。
なぜこのようになるのだろう。前回も述べたが、「変数やリストの要素は、オブジェクトに付けられる名札」のようなものだ。つまり、上のコードにある2つの変数languagesとlanguageの関係は次のようになっていると考えられる。
そして、変数への代入は「名札を別のオブジェクトに付け替える」操作と考えられる。簡単にいうと、ループ変数languageに代入をするのは、リストの要素から別のオブジェクトに名札を付け替えているようなものだ。すなわち、for文の一度目の繰り返し処理で「language = f'{counter}: {language}'」という代入文を実行して「0: Python」という文字列をループ変数に代入すると、その結果は次のようになる。
for文のループ変数languageには代入するたびに「0: Python」などの文字列が新たに作成されて、それが代入されるが、リストの要素は元の文字列を参照し続けるということだ。これが上のコードがうまく動かなかった理由である。ではどうすればよいかというと、「リストの要素」という名札を新しい文字列に付け替える必要がある。これにはインデックスを利用して「languages[0]」などに新たな文字列を代入すればよい。
簡単な方法としては、第10回「for文による繰り返し処理」の「for文とrange関数による繰り返し処理」で取り上げたrange関数と、リストの要素数を取得するlen関数を組み合わせるものが考えられる。
languages = ['Python', 'Ruby', 'PHP']
print(languages)
for index in range(len(languages)):
languages[index] = f'{index}: {languages[index]}'
print(languages)
range関数に「len(languages)」を渡すと、len関数の戻り値の「3」が渡されたことになる。このため、ここでは0〜2の整数値がループ変数indexに渡されることになる。for文の内部では「languages[index] = ……」とすることで、「リストの要素」という名札の付け替えを行っている。これで、リストの各要素を変更できるはずだ。
だが、これと同様な処理を行うための便利な関数がPythonには用意されている。次に、その関数の使用例を見てみよう。
Copyright© Digital Advantage Corp. All Rights Reserved.