Pythonistaなら積極的に使いたい内包表記。そして同じことを分かりやすく書けるforループ。内包表記とfor文の関係を理解しているかどうかを試してみませんか?
以下の2つのリスト内包表記をfor文に書き下したときに(1)から(4)に入るものを選択肢から選びなさい。なお、「 ? 」の部分は大ヒントになるので隠している。
r0 = [s0 + s1 for s0 in 'abc' for s1 in '012']
r1 = [[s0 + s1 for s0 in 'abc'] for s1 in '012']
# 内包表記をfor文に書き下すと……
r0 = []
for (1) in ? :
for (2) in ? :
r0.append(s0 + s1)
r1 = []
for (3) in ? :
tmp = []
for (4) in ? :
tmp.append(s0 + s1)
r1.append(tmp)
選択肢:
選択肢 | (1) | (2) | (3) | (4) |
---|---|---|---|---|
1 | s0 | s1 | s0 | s1 |
2 | s0 | s1 | s1 | s0 |
3 | s1 | s0 | s0 | s1 |
4 | s1 | s0 | s1 | s0 |
答えはどれでしょう? |
どうもHPかわさきです。Pythonistaなら使いたいですよね、内包表記。と思っていたのですが、どうもそうでもないこともあるようです。
先日の編集会議で、普段はRubyをメインに使っていて、たまにはPythonも使うよって人から「え? 内包表記? そんなによく使うの?」みたいなコメントをもらったんですよね。彼は筆者よりもプログラミングのスキルは高いし、使っている言語もたくさん、アイデアもあふれているしと、そりゃーもう素晴らしいプログラマー。そんな彼からそんな言葉を聞くとはちょっと意外だったのです。
でもよく考えてみると、他の言語でたくさんの経験があるプログラマーがちょこっとしたことをやりたいときに、(便利そうなフレームワークがたくさんあるという理由で)Pythonを手にしたらどうなんでしょう。Pythonに特有の内包表記なんて使わないで、これまでに自分が身に付けてきた知識と経験を基にしたコードを書くのは当たり前のような気もします。
このことについて特に結論はないんですが、取りあえず今回は内包表記の問題です。Pythonistaなら使いたいですよね?(そうでもない?)
答えは選択肢2の「(1)s0 (2)s1 (3)s1 (4)s0」です。分かりましたか? 正解のコード例を以下に示します。
r0 = [s0 + s1 for s0 in 'abc' for s1 in '012']
r1 = [[s0 + s1 for s0 in 'abc'] for s1 in '012']
# 内包表記をfor文に書き下すと……
r0 = []
for s0 in 'abc':
for s1 in '012':
r0.append(s0 + s1)
r1 = []
for s1 in '012':
tmp = []
for s0 in 'abc':
tmp.append(s0 + s1)
r1.append(tmp)
1つ目の内包表記は、その中でfor節がネストするもので、2つ目の内包表記はその式部分が内包表記になっている(内包表記がネストしている)ものです。この違いにより2つの変数s0とs1を使ったforループの順番が逆になっています。
まずは1つ目の内包表記について見てみましょう。以下はリスト内包表記と対応するfor文です。
r0 = [s0 + s1 for s0 in 'abc' for s1 in '012']
r0 = []
for s0 in 'abc':
for s1 in '012':
r0.append(s0 + s1)
書き下したコードを見ると、ネストしたfor節がそのままの順番でfor文に置き換えられていることが分かります。このことについては、Pythonのドキュメントに次のような記述があります。
内包表記はまず単一の式、続いて少なくとも1個のfor節、さらに続いて0個以上のfor節あるいはif節からなります。この場合、各々のfor節やif節を、左から右へ深くなっていくネストしたブロックとみなし、ネストの最内のブロックに到達するごとに内包表記の先頭にある式を評価した結果が、最終的にできあがるコンテナの各要素になります。
重要なのは「各々のfor節やif節を、左から右へ深くなっていくネストしたブロックとみなし」というところです。ここがストンと腑(ふ)に落ちれば、上のようなforループになることがすぐに分かるでしょう。
でも、「ドキュメントを読むだけだと意味がよく分からない」ってなりますよねぇ。多分、筆者はここを何度も読んでいるはずなのに、スカッと読み飛ばしていた気がします。ところがどっこい、コードと一緒にドキュメントを読んでみれば「ああ、なるほど!」となるので、ドキュメントの意味を具体的に示すコードはとても重要だなと思いました(小並感)。
そういえば、高校生の頃、数学の宿題が分からずに、友だちからノートを借りてまるっと写させてもらったことがあります。そのときには「なーんで、この式がこういうふうに変形するんだよ。頭のいいヤツが考えることは全く分からん」となったことがあります。でも、その部分の理解が進んで、同種の問題を自力で解いてみると友だちのノートのようにしか書けないことが分かったのです。それと通じるところがあるかもしれませんね(この文章、後から読み返してみましたが、そうでもないかな)。
ドキュメントの理解とコードの実践は、実力を上げる両輪みたいなものなのかもしれませんね。
要するに、内包表記でfor節が連続したときにはそれらはそのままの順番でfor文として書き下せると覚えておけばよいでしょう。
そして、2つ目の内包表記とそれを書き下したfor文は次のようなものでした。
r1 = [[s0 + s1 for s0 in 'abc'] for s1 in '012']
r1 = []
for s1 in '012':
tmp = []
for s0 in 'abc':
tmp.append(s0 + s1)
r1.append(tmp)
先ほどは「2つ目の内包表記はその式部分が内包表記になっている(内包表記がネストしている)ものです」と書きました。上で引用したPythonのドキュメントに当てはめてみると「内包表記はまず単一の式、続いて少なくとも1個のfor節、さらに続いて0個以上のfor節あるいはif節からなります」という部分の「単一の式」が内包表記になっていて、その後に続くfor節は1つだけと考えられます。
そのため、まずは外側の内包表記のfor節である「for s1 in '012'」が外側のfor文になり、単一の式である内包表記のfor節である「for s0 in 'abc'」が内側のfor文になったということですね。
ここまでくれば、要素の並び順が2つの内包表記で異なるのもすぐに分かるでしょう。1つ目の内包表記では、最初に文字列'abc'の各文字を見ていきます(外側のループ)。例えば'a'については、次に文字列'012'の各文字を見ていきます(内側のループ)。この順番で処理を進めることで、以下の表のように要素が作られます。
外側のループ | 内側のループ | 作られる要素 |
---|---|---|
'a' | '0'→'1'→'2' | 'a0', 'a1', 'a2' |
'b' | '0'→'1'→'2' | 'b0', 'b1', 'b2' |
'c' | '0'→'1'→'2' | 'c0', 'c1', 'c2' |
1つ目の内包表記 |
対して、2つ目の内包表記では、最初に文字列'012'の各文字を見ていきます(外側のループ)。例えば'0'については、次に文字列'abc'の各文字を見ていきます(内側のループ)。この順番で処理を進めることで、以下の表のように要素が作られます。
外側のループ | 内側のループ | 作られる要素 |
---|---|---|
'0' | 'a'→'b'→'c' | ['a0', 'b0', 'c0'] |
'1' | 'a'→'b'→'c' | ['a1', 'b1', 'c1'] |
'2' | 'a'→'b'→'c' | ['a2', 'b2', 'c2'] |
2つ目の内包表記 |
Pythonistaとしては積極的に使っていきたい内包表記ですが、慣れも必要です。これからもクイズの問題として取り上げていくので、実力を上げるために一緒に解いていきましょうね。
Copyright© Digital Advantage Corp. All Rights Reserved.