[Pythonクイズ]インデックスと値の両方が必要なとき、どうしますか?Pythonステップアップクイズ

リストの要素にはインデックスでアクセスできますが、for文ならインデックスなしでも反復が可能です。でも、要素とインデックスの両方が必要なときにはどうしたらよいでしょう。ちょっと思い出してみませんか。

» 2025年01月28日 05時00分 公開
[かわさきしんじDeep Insider編集部]
「Pythonステップアップクイズ」のインデックス

連載目次

カンタン過ぎる? それとも? カンタン過ぎる? それとも?

【問題】

 以下はリストの要素をそのインデックスと共に表示するPythonのコードである。このコードは問題なく動くが、もっとPythonらしくスマートに記述する方法がある。どうすればよいだろう?

mylist = ['a', 'b', 'c', 'e', 'f', 'g']

for idx in range(len(mylist)):
    print(f'{idx}: {mylist[idx]}')

インデックスと値の両方が必要なとき、どうする?


かわさき

 どうもHPかわさきです。

 今回は「カンタン過ぎる!」と思われるかもしれませんが、このネタにさせてもらいました。Pythonに入門したばかりの人にも知っておいてほしいイディオムの紹介ともいえます。とはいえ、エライ人からは「あれ? もうネタ切れ?」と言われたこともご報告しておきましょう。ネタ切れちゃいますよ。何も考えていないように見えて、いろいろ考えているんですよ(今日のお昼は何にしようかな? とか)。

 この手の「カンタン過ぎるかもしれない」ネタはこれからもたまに出していく予定です。よろしくお願いします(正直、手探り状態がまだ続いているのです)。


【答え】

 正解のコード例を以下に示します。

mylist = ['a', 'b', 'c', 'e', 'f', 'g']

for idx, value in enumerate(mylist):
    print(f'{idx}: {value}')

enumerate関数を使ってインデックスと値の両方を反復する

 正解のコード例では、enumerate関数を使って、リストのインデックスと対応する要素の両者をfor文のループ変数に取り出すようにすることで「range(len(mylist))」「mylist[idx]」のような書き方をしないようにしています。

range関数とlen関数の組み合わせはenumerate関数で置き換えられることがよくあるよ range関数とlen関数の組み合わせはenumerate関数で置き換えられることがよくあるよ

【解説】

 リストの要素にはインデックスを使って「リスト[インデックス]」のようにしてアクセスできます。インデックスを先頭から順番に反復するにはrange関数を使って「range(リストの要素数)」とすることで取り出せます。そして、リストの要素数はlen関数を使って「len(リスト)」とすれば得られます。これらを組み合わせたのが問題文にあるコードです。

mylist = ['a', 'b', 'c', 'e', 'f', 'g']

for idx in range(len(mylist)):
    print(f'{idx}: {mylist[idx]}')

len関数でリストの要素数を得て、それを使ってrange関数でインデックス範囲を指定して、リストの要素にインデックスアクセスする

 こうした書き方はPythonのイディオムともいえますが、リストの要素とインデックスの両者を反復することが目的であれば、enumerate関数を使うのがPythonではより一般的な書き方です。

mylist = ['a', 'b', 'c', 'e', 'f', 'g']

for idx, value in enumerate(mylist):
    print(f'{idx}: {value}')

enumerate関数を用いて書き直したコード

 enumerate関数は反復可能なオブジェクトを受け取り、そのインデックスと要素を「(インデックス, 要素)」というタプルとして反復してくれます。タプルとして反復されたインデックスと要素は「for idx, value in enumerate(...):」のように書くことで、個別のループ変数に取り出せることもポイントです。


かわさき

 こんな書き方をすることで、「range(len(mylist))」と書くことでどことなくスッキリしない気持ちがスッキリするようになってきたら、Pythonプログラマーとしてちょこっとステップアップしたといえるかもしれませんね。


 とはいえ、「range(len(...))」という書き方が全くダメというわけではないことには注意しましょう。例えば、for文の中で何らかの条件を基にその要素の値を変更したいとします。

mylist = [0, 1, 2, 3, 4]

for value in mylist:
    if value % 2 == 0:
        value = value * 2

print(mylist)

要素の値が2の倍数なら、それを2倍したものに書き換える(つもりの)コード

 例えば、上のコードはリストの要素を反復して、その値が2の倍数なら、それを2倍した値に変更するつもりのコードです。しかし、これを実行すると次のような結果になります。

変更したつもりなのにリストの内容が変わっていない! 変更したつもりなのにリストの内容が変わっていない!

 for文の反復のたびに、ループ変数(value)にはリストの要素が順番に代入されていきます。しかし、ループ変数の値を書き換えるというのは「リストの要素を書き換える」とは意味が違うのです。書き換える前のループ変数は、リストの要素を参照していましたが、書き換えることでそのループ変数が別のオブジェクトを参照するようになるだけです。

 以下は上のコードでループが進み、valueに4が代入されたところを図にしたものです。

ループ変数valueへの代入は、valueが別のオブジェクトを指すようにするだけ ループ変数valueへの代入は、valueが別のオブジェクトを指すようにするだけ

 このとき、「value *= 2」で何が行われるかというと、変数valueが参照する先が「8」というint型のオブジェクトになるだけで、リストの要素(mylist[4])の参照する先はそのままです。

 というわけで、リストの要素を変更したいのであれば、やはりインデックスによるアクセスが必要になります。こんなときには「range(len(...))」という書き方が合っているかもしれません。

mylist = [0, 1, 2, 3, 4]

for idx in range(len(mylist)):
    if mylist[idx] % 2 == 0:
        mylist[idx] *= 2

print(mylist)

リストの要素を変更するならインデックスアクセスが必要

 とはいえ、これはenumerate関数を使って次のようにも書けます。

mylist = [0, 1, 2, 3, 4]

for idx, value in enumerate(mylist):
    if value % 2 == 0:
        mylist[idx] *= 2

print(mylist)

同じことをenumerate関数を使って記述できる

 あるいはリスト内包表記を使うのがいい、という人もいるでしょう。

mylist = [0, 1, 2, 3, 4]

mylist = [value * 2 if value % 2 == 0 else value for value in mylist]
print(mylist)

リスト内包表記を使って同じことをする

 これらのコードのどちらがよいかの判断は筆者にはつきません(リスト内包表記は使わないかなぁ。if式を内包表記で使うと長くなりますよね)。リストの要素とインデックスを取得することが主であればenumerate関数を使って、リストの要素の変更までを考えるのであれば「range(len(...))」という書き方もアリくらいに考えておけばよいでしょう。


かわさき

 変数が参照であることで生まれるあんなことやそんなことについては、「忘れた頃に問題に出すからね」(ビシッ!)ということにしましょう。

 そして、enumerate関数については「Python入門」の「リストと繰り返し処理」でも解説をしています。詳しいことはそちらを参照してください。


「Pythonステップアップクイズ」のインデックス

Pythonステップアップクイズ

Copyright© Digital Advantage Corp. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

4AI by @IT - AIを作り、動かし、守り、生かす
Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

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

メールマガジン登録

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