Python変数の参照とコピー:copyモジュールで予期せぬ変更を防ぐ

目次

参照とは?

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リストも同じように変更されました。これは、itemsfruitsが同じオブジェクトを指し示しているためです。


関数への参照渡し

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()関数を使って、明示的にリストのコピーを作成する必要があります。

この参照とコピーの仕組みを理解することで、予期せぬバグを防ぎ、より堅牢なプログラムを書くことができます。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次