検索
連載

[Pythonクイズ]Pythonの代入文の不思議 このコードの実行結果分かりますか?Pythonステップアップクイズ

Pythonの代入は式じゃなくって文で行います。そこから生まれる(?)ちょっとした不思議な挙動について皆さんも考えてみませんか?

PC用表示 関連情報
Share
Tweet
LINE
Hatena
「Pythonステップアップクイズ」のインデックス

連載目次

どんな出力になるかな?
どんな出力になるかな?

【問題】

 以下のコードを実行すると、なんと出力されるだろうか?

a = 'a'
b = a, c = 'bc'
print(a, b, c)  # ?

どんな出力になるかな?

3つのLLMもギブアップ?

 さて今回は素直にChatGPT、Gemini、Claudeの3つのLLMがこの問題を解けるかどうかを試してみました。今回は全員が不正解! なので、皆さんも正解できなかったところで問題ナッシングですよ。

 では、3つのLLMはどんなところを間違えたのでしょうか。解説が終わった後にお話ししましょう。



かわさき

 どうもHPかわさきです。

 今回もあちこちらこちらを放浪しながら、ネタを探していて、自分でも気が付いていなかったことを取り上げました。Pythonの代入が式じゃなくって文なことは分かっていたんですがね……そんな風になるとは思いもしませんでした。そういうわけでお楽しみいただければ幸いです。


【答え】

 問題文のコードを実行すると「b bc c」と出力されます。

例外は発生しないよ?
例外は発生しないよ?

 問題文のコードはPythonの連鎖代入(=が複数ある代入文)とアンパック代入が組み合わされたことで分かりにくくなっています。代入が実際にどういう順序で行われるかというと、「b = 'bc'」が実行された後に、「a, c = 'bc'」が実行されるのです。

 なんで? って思いますよね? え? 思わない?

【解説】

 というわけで解説です。その前に問題文のコードを再掲します。

a = 'a'
b = a, c = 'bc'
print(a, b, c)  # ?

問題文のコード(再掲)

 1行目はいいですよね。変数aに文字列'a'が代入されるだけです。

 問題は2行目です。「意味分からん!」ってなりそうです。自分の頭の中にあるPythonインタプリターなら例外を出したくなるところです。C言語などに慣れた方は、これは「a, c = 'bc'」が実行されてから「b = a, c」が実行されるんだろ? と思うかもしれませんね。Pythonでは実はそうじゃないんです。

 そうじゃないんですが、取りあえず「a, c = 'bc'」について説明しておきましょう。これはいわゆるアンパック代入です。というのは、文字列は反復可能オブジェクトだからです。これがタプルならすぐに分かるはず。

a, c = 'b', 'c'  # 'b', 'c'は('b', 'c')のかっこを省略したもの

反復可能オブジェクトはアンパックして複数の変数に代入可能

 文字列もまたタプルやリストと同じく反復可能オブジェクトなので、それを1文字ずつに分割して、(文字数と同じ数の)変数にアンパック代入できます。そのため、変数aには'b'が、変数cには'c'が代入されるというわけです。

 では、「a, c = 'bc'」の次に「b = a, c」が行われるとしたら、変数bの値は「('b', 'c')」というタプルになりそうです。が、実際には変数bの値は'bc'になります。

 「ホントかよぉ」と思う方のために、実行結果も貼っておきましょう。

実行結果。変数bの値は'bc'
実行結果。変数bの値は'bc'

 なぜでしょう。それはPythonでは「b = a, c = 'bc'」という代入文があったときには、「b = 'bc'」が実行された後に、「a, c = 'bc'」が実行されるからです。

 試してみますか? ということで、以下のコードを書いてみました。

import dis

def foo():
    b = a, c = 'bc'
    
dis.dis(foo)

「b = a, c = 'bc'」

 「b = a, c = 'bc'」を本体とする関数をdis.dis関数で逆アセンブルしてバイトコードを表示してみましょう。

バイトコードを見ると、変数bへの代入が先に行われていることが分かる
バイトコードを見ると、変数bへの代入が先に行われていることが分かる

 このバイトコードの振る舞いをざっくりと説明すると次のようになります。

  1. 文字列'bc'をスタックにプッシュする
  2. そのコピーをスタックにプッシュする
  3. スタックの先頭要素を変数bに保存する
  4. スタックに置かれた'bc'をアンパックして2つの値にする
  5. 変数aとcにアンパックされた値を保存する

 このように先に変数bに'bc'が代入されてから、変数aとcへアンパック代入されるのです。Pythonの代入文は「最右辺の値が一度評価された後、ターゲット(左辺)に個別に代入される」と考えるのが適切なようです。つまり、「b = a, c = 'bc'」は次のようなコードだと考えるのがよいでしょう。

# b = a, c = 'bc'
b = 'bc'
a, c = 'bc'

連鎖代入はそれぞれの代入をバラして考えるのがよい


かわさき

 3つのLLMの全てが今回の問題を正解できなかったのですが、どんな感じだったかをまとめましょう。

  • ChatGPTさん:SynntaxErrorになる。揚げ句、実行できたのは筆者がタイプミスしたからだと結論(ひどいな、おい)
  • Geminiさん:ValueErrorになる。Pythonの代入文(連鎖代入)は右結合とも述べている
  • Claudeさん:Geminiさんと同じく、Pythonの連鎖代入は右結合と述べている

 代入が右結合だと考えていたのが主な原因でしょう。

 なお、混乱の度合いが一番ひどかったのはGeminiさんでした。

混乱した様子のGeminiさん
混乱した様子のGeminiさん

 筆者としては、うまく3つのLLMを出し抜けて楽しかったです。


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

Pythonステップアップクイズ

Copyright© Digital Advantage Corp. All Rights Reserved.

[an error occurred while processing this directive]
ページトップに戻る