Python♪本当は変数には参照先のアドレス(id)しか入っていない

変数aに整数3を代入するとは、aという名前の箱に整数3を入れるようなイメージですが、実際にはそうではありません。整数オブジェクト3が別のところに独立して生成され、変数aには整数オブジェクトの保存先のアドレス(id)のみが記述されます。この記事では、ミュータブル、イミュータブルの例によりその仕組みを説明します。

1.関連記事

この記事は、以下の記事を「本当は変数には参照先のアドレス(id)しか入っていない」ことを説明するために修正したものです。説明する内容はほとんど重複していますが、説明で用いる図が違います。

元記事の方が多少読みやすいかもしれませんが、この記事の方が 変更不能体とは何が変更できないのかはっきりすると思います。

用語集:変更可能体(ミュータブル)と変更不能体(イミュータブル)

2.変更可能体と変更不能体の例 

変更可能体(ミュータブル)と変更不能体(イミュータブル)の例は以下の通りです。

(1) 変更可能体:リスト、セット、ディクショナリ、bytearray型
(2) 変更不能体:タプル、数値型、文字列、ブール型、フローズンセット、bytes型

3.変更不能体とはjavaの定数とは違う

変更不能体とはJavaやCの「定数」とは異なります。JavaやCの定数のように、その変数が変更できなくなるのではないのです。整数やタプルは変更不能体(イミュータブル)ですが、コード01のようにそっくり入れ替えれば変更することが可能です。「変更できない」の意味が違うので注意しましょう。

#コード01
a = 3
b = (2, 3)
print(a, b)
a = 5
b = (5, 3)
print(a, b)
#出力01
3 (2, 3)
5 (5, 3)

「変更不能体とは部分的に変更できないもの」と覚えてもよいのかもしれません。例えば、タプルは変更不能体ですので、コード02のような部分的な変更はできません。コード02は3行目でエラーになります。整数については、そもそも整数の一部というものがないので部分的に変更することが不可能です。

しかし、本当はこの説明では「変更不能」の意味をモヤッと説明しています。

#コード02
b = (2, 3)
b[0] = 5

もう少し踏み込んだ説明をすると、例えばコード02の例では変数aは書き換え可能ですが、変数aが参照しているタプルオブジェクトが変更できないのです。

4.整数が代入された変数の変更

コード03 は、整数3が代入された変数aの内容を整数5に変更するだけのシンプルなコードです。なお、整数は変更不能体(イミュータブル)です。

#コード03
a = 3
a = 5

よく書籍などで見る説明では変数aという箱に整数3をいれてあるような図が多いですが、ここでは実際のコンピューター内部のしくみをもう少し詳しく説明します。

意味もなく趣味で難しい部分に踏み込んでいるように感じるかもしれませんが、Pythonでは、このしくみを理解する方が近道になると思いますので、是非、最後まで読んでみてください。

(1) 整数オブジェクトの生成

それでは、下図を見てください。コード03の2行目の「a = 3」では最初に右辺の「3」の部分が実行され、整数3がコンピューターのメモリーのi番地に記憶されます。この整数3のことを整数オブジェクトといいます。整数オブジェクトは変数aとは独立して存在しています。

(2) 変数aに参照先を代入

次に、生成した整数3が左辺の変数aに代入されますが、変数aには整数3そのものが記憶されるのではなく、整数3が保存されている場所(参照先)のみが代入されます。

このように、Pythonではどの様な場合でも基本的に変数には参照先のみが代入されているのです。

そして、例えば「y = a * a」のように変数aを使う場合には、まず、変数aに記述されているi番地のオブジェクトが参照され、その結果、参照先に記憶されている整数3が呼び出されます。

(3) 変数aの再定義

では、変数aの内容を変更してみましょう。コード03の3行目の「a = 5」では、変数aが5に変更されていますが、整数オブジェクトは変更不能体であるため、i番地の整数オブジェクトの内容は変更することができません。そこで、別のk番地に新たに整数オブジェクト5を生成し、変数aの参照先もi番地からk番地に更新します。

つまり、「a = 5」では整数オブジェクトの内容が書き換えられているのではなく、i番地の整数3はそのまま放置し、変数aを全く新しい内容に再定義しているのです。

このように、変更不能体(イミュータブル)とは変数aが変更できないのではなく、変数aが参照している整数オブジェクトが変更できないのです。

5.タプルが代入された変数の変更

変更不能体(イミュータブル)であるタプルが代入された変数の場合も全く同様です。

#コード04
a = (2, 3)
a = (5, 3)

(1) タプルオブジェクトの生成

コード05を図示すると以下のようになります。

まずは、2行目のa = (2, 3)の右辺(2, 3)の部分が実行されタプルオブジェクトが生成されます。

(2) 変数aに参照先を代入

次に、変数aにはタプルオブジェクトを記憶した参照先のアドレスが代入されます。これで変数aから(2, 3)が呼び出せるようになります。

(3) 変数aの再定義

3行目では変数aに(5, 3)を代入しますが、タプルオブジェクトも変更不能体(イミュータブル)なので変更できず、i番地のタプルオブジェクトはそのまま放置し、変数aを再定義します。

6. リストが代入された変数の変更

リストは整数やタプルと異なり、変更可能体(ミュータブル)です。したがって、要素の変更が可能です。コード05を図で説明したいと思います。

#コード05
a = [2, 3]
a[0] = 5

(1) リストオブジェクトの生成

変数aへのリストの代入は同様です。まず、右辺の[2, 3]が実行され、リストオブジェクトが生成されます。

(2) 変数aに参照先を代入

次に、変数aにはリストオブジェクトを記憶した参照先のアドレスが代入されます。

(3) 変数aの要素の変更

コード05の3行目の「a[0] = 5」では要素の変更を行っています。リストは変更可能体であるため、リストオブジェクトを直接変更することができます。したがって、変数aの参照先を変更することなく要素の変更を行うことが可能です。

7.リストが代入された変数の再定義

なお、コード05の3行目のように「a = [5, 3]」とするとどうなるのでしょうか。

#コード05
a = [2, 3]
a = [5, 3]

下図のように、i番地のリストオブジェクト[2, 3]は放置し、k番地の[5, 3]に再定義します。

リストの場合、左辺が「a[0] =」のような要素を示す記述の場合には参照先のリストオブジェクトが変更されますが、「a =」のように左辺が変数名だけの場合には変数が再定義されます。

このように「変数名 = 値」という書式の場合には、変更可能体(ミュータブル)であっても変数が再定義されます。

この違いを理解すると、参照渡し、浅いコピー、深いコピー、関数の引数などの考え方を整理する上で役にたつと思います。是非、覚えておいてください。

私が実際に購入した教材のご紹介

以下、私が実際に購入したPythonの教材をまとめてみました。 Pythonを学習する上で、少しでもお役に立つことができればうれしいです。

Python♪私が購入したPythonの書籍のレビュー

UdemyのPythonの動画講座を書籍を買う感覚で購入してみた