【ITニュース解説】Python Mystery Quiz: Can You Crack This Code?
2025年09月11日に「Dev.to」が公開したITニュース「Python Mystery Quiz: Can You Crack This Code?」について初心者にもわかりやすく解説しています。
ITニュース概要
Pythonの変数は、値の箱ではなく「オブジェクトを指す名前」だ。整数などは「変更できない(イミュータブル)」ため新しいオブジェクトができるが、リストなどは「変更できる(ミュータブル)」ため元のオブジェクトが書き換わる。この違いがPythonコードの挙動を理解する鍵となる。
ITニュース解説
Pythonプログラミングにおいて、一見すると単純に見えるコードが、実は多くの開発者を戸惑わせる挙動を示すことがある。その謎を解き明かす鍵となるのが、「ミュータブル(Mutable)」と「イミュータブル(Immutable)」というオブジェクトの性質である。この概念を理解することは、Pythonコードの予期せぬ動作を防ぎ、より堅牢なプログラムを作成するために不可欠である。
まず、次のPythonコードを見て、その出力がどうなるか考えてみよう。
1a = 500 2b = a 3a = a + 100 4 5list1 = [1, 2] 6list2 = list1 7list1.append(3) 8 9print(f"b is {b}") 10print(f"list2 is {list2}")
このコードの出力は、多くの人が予想する「b is 600 and list2 is [1, 2, 3]」や「b is 500 and list2 is [1, 2]」ではない。正解は「b is 500 and list2 is [1, 2, 3]」となる。なぜこのような結果になるのかを、Pythonがオブジェクトをどのように扱っているかという視点から詳しく見ていく。
この異なる挙動は、Pythonが扱うオブジェクトの性質によって決まる。全てのオブジェクトは、作成後にその内容を変更できる「ミュータブル」なものと、一度作成されたら内容を変更できない「イミュータブル」なもののいずれかに分類される。
整数(int)の挙動:イミュータブルなオブジェクト
まず、整数の部分を見てみよう。
1a = 500 2b = a 3a = a + 100
a = 500: これは、値が500である整数オブジェクトをメモリ上に作成し、変数名aがそのオブジェクトを指し示すようにする。b = a: 変数bも、変数aが指し示しているのと同じ500の整数オブジェクトを指し示すようになる。この時点では、aとbは同じオブジェクトを共有している。a = a + 100: ここが重要である。a + 100という計算が行われると、現在のaが指す500という値に100が加算され、結果として600という新しい値が導き出される。Pythonは、この新しい値である600を持つ「新しい整数オブジェクト」をメモリ上に作成する。そして、変数aは、もはや最初の500のオブジェクトを指すのをやめ、この新しく作られた600のオブジェクトを指し示すように変更される。
整数はイミュータブルなオブジェクトであるため、一度作成された整数オブジェクトの値を直接変更することはできない。a = a + 100 という操作は、既存の500のオブジェクトを変更するのではなく、新しい600のオブジェクトを作成し、a をその新しいオブジェクトに再割り当てする、という流れになる。
この結果、変数 b は一度も再割り当てされていないため、引き続き最初に作成された500の整数オブジェクトを指し示し続ける。したがって、b の値は500のままとなる。
リスト(list)の挙動:ミュータブルなオブジェクト
次に、リストの部分を見てみよう。
1list1 = [1, 2] 2list2 = list1 3list1.append(3)
list1 = [1, 2]: これは、要素1と2を持つリストオブジェクトをメモリ上に作成し、変数名list1がそのオブジェクトを指し示すようにする。list2 = list1: 変数list2も、変数list1が指し示しているのと同じリストオブジェクトを指し示すようになる。この時点では、list1とlist2は同じリストオブジェクトを共有している。list1.append(3): ここが整数と異なる点である。append()メソッドは、既存のリストオブジェクトに対して直接変更を加える操作である。つまり、メモリ上にあるリストオブジェクトそのものに、新たに3という要素が追加される。この操作は、新しいリストオブジェクトを作成するわけではない。
リストはミュータブルなオブジェクトであるため、その内容を後から変更することが可能である。append() メソッドは、この性質を利用して、既存のリストオブジェクトの内容を直接変更する。
結果として、list1 と list2 の両方が同じリストオブジェクトを指し示しており、そのオブジェクトの内容が append(3) によって変更されたため、どちらの変数からアクセスしても、リストは [1, 2, 3] という状態になっている。したがって、list2 の値は [1, 2, 3] となる。
変数は「名前」であり「箱」ではない
この挙動の違いを理解するための最も重要な考え方は、「Pythonにおける変数は、値そのものを格納する『箱』ではなく、メモリ上にある『オブジェクト』を指し示す『名前(参照)』である」ということである。
- イミュータブルなオブジェクト(整数、文字列、タプルなど)の場合:
- 変数への操作(例:
a = a + 100)が、そのオブジェクトの内容を変更しようとすると、Pythonは新しい内容を持つ新しいオブジェクトをメモリ上に作成する。 - そして、変数は以前のオブジェクトを指すのをやめ、新しく作成されたオブジェクトを指し示すように「名前を付け替える」のである。元々同じオブジェクトを指していた別の変数は、この名前の付け替えの影響を受けないため、以前のオブジェクトを指し示す状態のままである。
- 変数への操作(例:
- ミュータブルなオブジェクト(リスト、辞書、セットなど)の場合:
- 変数への操作(例:
list1.append(3))が、そのオブジェクトの内容を変更しようとすると、Pythonは既存のオブジェクトの「中身」を直接変更する。 - この場合、新しいオブジェクトは作成されない。したがって、そのオブジェクトを指し示している全ての変数は、変更後の同じオブジェクトを指し示すことになる。
- 変数への操作(例:
なぜこの概念が重要なのか
ミュータブルとイミュータブルの理解は、単なる知識としてだけでなく、実際のプログラム開発において、予期せぬバグを防ぐ上で非常に重要である。特に、関数に引数を渡す際や、デフォルト引数にミュータブルなオブジェクトを使用する際には注意が必要である。
例えば、次のコードは典型的な「落とし穴」である。
1def add_item(item, target_list=[]): 2 target_list.append(item) 3 return target_list 4 5list1 = add_item("first") 6list2 = add_item("second")
このコードを実行すると、list2 の値は ["first", "second"] となる。これは、関数のデフォルト引数 target_list=[] が、関数が定義されたときに一度だけ作成されるミュータブルなリストオブジェクトを指しているためである。関数が複数回呼び出されるたびに、同じリストオブジェクトが共有され、その内容が変更されてしまう。この挙動は、ミュータブルなオブジェクトがどのように機能するかを理解していれば予測可能となる。
理解度チェック:文字列と辞書
最後に、この概念を適用して次のコードの出力を予測してみよう。
1x = "hello" 2y = x 3x = x + " world" 4 5dict1 = {"key": "value"} 6dict2 = dict1 7dict1["key"] = "modified" 8 9print(f"y is '{y}'") 10print(f"dict2 is {dict2}")
答えは「y is 'hello' and dict2 is {'key': 'modified'}」である。文字列(str)は整数と同様にイミュータブルなオブジェクトであり、x = x + " world" は新しい文字列オブジェクトを作成する。一方、辞書(dict)はリストと同様にミュータブルなオブジェクトであり、dict1["key"] = "modified" は既存の辞書オブジェクトの内容を直接変更する。
Pythonの変数システムは、このミュータブルとイミュータブルという根底にあるモデルを理解することで、その挙動が非常に予測しやすくなる。変数がオブジェクトそのものではなく、オブジェクトを指し示す「名前」であるという考え方を習得すれば、多くの「Pythonの謎」が解き明かされ、より効果的にデバッグを行い、信頼性の高いコードを書くことができるだろう。この概念は、Pythonプログラミングの基礎として、システムエンジニアを目指す上ではぜひともマスターすべき重要な知識である。