参照とは?
Pythonでは、変数はデータそのものを直接格納しているわけではなく、メモリ上のデータが保存されている場所を指し示す「参照(リファレンス)」として機能します。
この仕組みは、データ型が**イミュータブル(変更不可能)**か、**ミュータブル(変更可能)**かによって、異なる挙動を生み出します。
ミュータブルとイミュータブルな参照の違い
1. イミュータブルなデータ(数値など)の場合
数値や文字列はイミュータブルです。そのため、変数に新しい値を代入すると、Pythonは新しいデータ領域を作り、変数はそちらを参照するようになります。
# 変数 number_one を作成し、10 を参照
number_one = 10
# number_two に number_one の参照をコピー
number_two = number_one
# number_one に新しい値を代入
number_one = 20
print(f"number_one: {number_one}") # 出力: 20
print(f"number_two: {number_two}") # 出力: 10
number_one
の値を変えても、number_two
の値は変わりません。これは、number_one = 20
の時点でnumber_one
が新しい数値20
の参照に切り替わったためです。
2. ミュータブルなデータ(リストなど)の場合
一方、リストはミュータブルです。リストを別の変数に代入すると、二つの変数は同じリストを参照するようになります。
# リスト items を作成
items = ["apple", "banana", "orange"]
# items の参照を fruits にコピー
fruits = items
# fruits を介してリストの内容を変更
fruits[1] = "grape"
print(f"items: {items}") # 出力: ['apple', 'grape', 'orange']
print(f"fruits: {fruits}") # 出力: ['apple', 'grape', 'orange']
fruits
を介してリストの2番目の要素を変更したところ、元のitems
リストも同じように変更されました。これは、items
とfruits
が同じオブジェクトを指し示しているためです。
関数への参照渡し
Pythonでは、関数に引数を渡す際も、同様に参照が渡されます。ミュータブルなオブジェクトを引数として渡した場合、関数内で行われた変更は、関数の外にある元のオブジェクトにも影響します。
def add_new_item(some_list):
# some_list は、関数の外にある original_list と同じリストを参照
some_list.append("new_item")
original_list = ["item1", "item2"]
print(f"関数呼び出し前: {original_list}")
add_new_item(original_list)
print(f"関数呼び出し後: {original_list}")
実行結果:
関数呼び出し前: ['item1', 'item2']
関数呼び出し後: ['item1', 'item2', 'new_item']
関数内でappend
メソッドが実行された結果、関数の外にあるoriginal_list
も変更されていることが分かります。
copy
モジュールによるコピー
このような意図しない変更を防ぐには、リストの「本当のコピー」を作成する必要があります。これには、Pythonの標準ライブラリである**copy
モジュール**を使います。
copy.copy()
:浅いコピー(Shallow Copy)
copy()
関数は、リストの中身を新しいリストにコピーします。これは**浅いコピー(shallow copy)**と呼ばれ、元のリストの要素を指す新しい参照を持つリストを作成します。
import copy
source_list = ["A", "B", "C"]
# copy()関数を使って、新しいリストを作成
new_list = copy.copy(source_list)
# 新しいリストの要素を変更
new_list[1] = "new_B"
print(f"元のリスト: {source_list}") # 出力: ['A', 'B', 'C'] (元のリストは変更されない)
print(f"新しいリスト: {new_list}") # 出力: ['A', 'new_B', 'C']
この例のように、浅いコピーでは、元のリストと新しいリストは完全に独立しており、一方が変更されても他方は影響を受けません。
補足:
copy.deepcopy()
(深いコピー)リストの中に別のリストや辞書などのミュータブルなオブジェクトが含まれている場合(入れ子構造)、浅いコピーでは内部のミュータブルオブジェクトはまだ参照を共有しています。このような場合に備え、**
copy.deepcopy()
**を使うことで、入れ子になった全てのオブジェクトを再帰的にコピーし、完全に独立したコピーを作成できます。
まとめ
- Pythonの変数は、データへの参照です。
- イミュータブルなデータ型(数値、文字列)は、代入時に値がコピーされます。
- ミュータブルなデータ型(リスト)は、代入時に参照がコピーされるため、同じオブジェクトを共有します。
- 関数にミュータブルなオブジェクトを渡すと、関数内での変更が元のオブジェクトに影響します。
- 意図しない変更を防ぐためには、
copy
モジュールのcopy()
関数を使って、明示的にリストのコピーを作成する必要があります。
この参照とコピーの仕組みを理解することで、予期せぬバグを防ぎ、より堅牢なプログラムを書くことができます。