プログラムの基本　共有した値の変更について

### Pythonの参照渡しの記事を書こうとしたら、早速間違い指摘されたので・・・

どうやら、Pythonでは参照渡しという言葉は厳密には正しくないようです。
「オブジェクト参照渡し (call-by-object-reference)」または「共有渡し (call-by-sharing)」が正しいようです。

イミュータブルかミュータブルか、変数に代入しているのか変数を参照してオブジェクト内に代入しているのかを理解・把握する必要があります。
- ミュータブル (Mutable):
内容を変更**できる**オブジェクトです。
代入で値が共有されます。
内容を変更すると、共有した変数に影響します。
累算代入では、新しいオブジェクトを**生成せずにオブジェクト内容を変更**します。
しいオブジェクトを変数に再代入すれば共有が解除され、他の変数に影響しません。
例：リスト (list)、辞書 (dict)
- イミュータブル (Immutable):
内容を変更**できない**オブジェクトです。**イミュータブル部分を変更すると例外が発生します**。イミュータブル内のミュータブル部分は変更できます。
代入で値が共有されます。
イミュータブル内のミュータブル部分を変更すると、共有した変数に影響します。
イミュータブル要素を変更するには、新しいオブジェクトを作成して変数に再代入する必要があります。
累算代入では、新しいオブジェクトを**生成して変数に再代入**します。
新しいオブジェクトを変数に再代入すれば共有が解除され、他の変数に影響しません。
例：数値型 (int, float)、文字列 (str)、タプル (tuple)

以下、上記の知識の元で記事を読み進めて下さい。


### 分かっていてもつい間違っちゃう共有した値の変更。

この辺は多くの言語でほぼ共通の挙動なだけど、特にpythonの変数やリストや辞書などの内部はすべて値（オブジェクト、データ）への参照を保持するので、代入で値を共有してしまい、変更するつもりのない値を共有して変更されてしまうバグを作ってしまう事があります。😅

というか、割とこの辺の挙動は人間の感性に合ってない記述のような気がしていて、勘で書くとこの辺の罠に引っ掛かかるので、よく考えて回避しながら書く必要があるイメージ？😗

copyは浅いコピーだから参照が共有されて元の値を変更。deepcopyは共有なしだから元の値を変更しないとか引っ掛け問題みたいなものもあるし。💢

自分への戒めのためにもその挙動を記します。
※イミュータブルオブジェクトの挙動についてもテストを追加しました。

結論としては下記のとおり。ミュータブルであろうとイミュータブルであろうと同じ。
- 共有した値の内部を編集→元の値が変わる
- 変数への再代入→元の変数・値に影響なし
- copyはこの辺の対策に微妙。元の値と完全に切り離すにはdeepcopyを使う。

``` python
import copy

def modify_get_value(settings):
    datas = settings.get("datas",{})
    datas["data_a"] = 2  # 呼び出し元に影響あり（ミュータブル操作）

def modify_inner_value(settings):
    settings["datas"]["data_a"] = 3  # 呼び出し元に影響あり

def overwrite_inner_dict(settings):
    settings["datas"] = {"data_a": 4}  # 呼び出し元に影響あり（辞書の再代入）

def overwrite_settings(settings):
    settings = {"datas": {"data_a": 5}}  # 呼び出し元に影響なし（settingsの再代入）

def overwrite_settings_copy1(settings):
    settings_copy = settings.copy()
    settings_copy["datas"]["data_a"] = 6  # 呼び出し元に影響あり（浅いコピーでは、トップレベルのオブジェクトはコピーされますが、内部のミュータブルなオブジェクト（この場合は辞書 datas）は元のオブジェクトを参照したままになります）

def overwrite_settings_copy2(settings):
    settings_copy = settings.copy()
    settings_copy["datas2"] = 7 # 呼び出し元に影響なし（settingsはコピーできているので、settingsの変更は元に影響しない。）

def overwrite_settings_deepcopy(settings):
    settings_copy = copy.deepcopy(settings)
    settings_copy["datas"]["data_a"] = 8  # 呼び出し元に影響なし（deepcopy）

def update_inner_dict(settings):
    settings["datas"].update({"data_a": 9})  # 呼び出し元に影響あり（updateは中身を直接変更）

def overwrite_inner_dict_with_update(settings):
    new_dict = {}
    new_dict.update({"data_a": 10})
    settings["datas"] = new_dict  # 呼び出し元に影響あり（結局再代入）

def overwrite_value(settings):
    settings = 11 # 関数内での再代入

def try_modify_tuple(data_tuple):
    try:
        data_tuple[0] = 100 # イミュータブルなのでエラーになる
    except TypeError as e:
        print(f"タプルの直接変更を試みましたがエラー: {e}")

def change_tuple_indirectly_concat(data_tuple):
    try:
        new_tuple = data_tuple[:1] + (100,) + data_tuple[2:]
        return new_tuple
    except TypeError as e:
        print(f"タプルの変更（連結）でエラー: {e}")
        return data_tuple

def change_tuple_indirectly_list(data_tuple):
    try:
        data_list = list(data_tuple)
        data_list[1] = 200
        new_tuple = tuple(data_list)
        return new_tuple
    except TypeError as e:
        print(f"タプルの変更（リスト変換）でエラー: {e}")
        return data_tuple

def main():
    print("--- ミュータブルなオブジェクト (辞書) の場合 ---")
    settings = {"datas": {"data_a": 1}}
    print("初期値:", settings)

    settings = {"datas": {"data_a": 1}} # settingsをリセット
    modify_get_value(settings)
    print("getした値を変更:", settings)

    settings = {"datas": {"data_a": 1}} # settingsをリセット
    modify_inner_value(settings)
    print("内部の値を変更:", settings)

    settings = {"datas": {"data_a": 1}} # settingsをリセット
    overwrite_inner_dict(settings)
    print("内部の辞書を上書き:", settings)

    settings = {"datas": {"data_a": 1}} # settingsをリセット
    overwrite_settings(settings)
    print("settingsを上書き:", settings)

    settings = {"datas": {"data_a": 1}} # settingsをリセット
    overwrite_settings_copy1(settings)
    print("settingsの浅いコピーを変更:", settings)

    settings = {"datas": {"data_a": 1}} # settingsをリセット
    overwrite_settings_copy2(settings)
    print("settingsの浅いコピーに新しいキーを追加:", settings)

    settings = {"datas": {"data_a": 1}} # settingsをリセット
    overwrite_settings_deepcopy(settings)
    print("settingsのdeepcopyを変更:", settings)

    settings = {"datas": {"data_a": 1}} # settingsをリセット
    update_inner_dict(settings)
    print("updateで中身変更:", settings)

    settings = {"datas": {"data_a": 1}} # settingsをリセット
    overwrite_inner_dict_with_update(settings)
    print("updateで作った辞書で上書き:", settings)

    print("\n--- イミュータブルなオブジェクト (数値) の場合 ---")
    num = 1
    print("初期値:", num)
    overwrite_value(num)
    print("数値を上書きしたつもり:", num)

    print("\n--- イミュータブルなオブジェクト (文字列) の場合 ---")
    text = "ABC"
    print("初期値:", text)
    overwrite_value(text) # 関数内では再代入されるが、元の変数は変わらない
    print("文字列を上書きしたつもり:", text)

    print("\n--- イミュータブルなオブジェクト (タプル) の場合 ---")
    data = (1, 2, 3)
    print("初期値:", data)
    try_modify_tuple(data)
    result = change_tuple_indirectly_concat(data)
    print("連結後のタプル:", result)
    print("連結後のタプル(引数への変更なし):", data)
    result = change_tuple_indirectly_list(data)
    print("リスト変換後のタプル:", result)
    print("リスト変換後のタプル(引数への変更なし):", data)

if __name__ == "__main__":
    main()
```

結果は下記のとおりです。
```
--- ミュータブルなオブジェクト (辞書) の場合 ---
初期値: {'datas': {'data_a': 1}}
getした値を変更: {'datas': {'data_a': 2}}
内部の値を変更: {'datas': {'data_a': 3}}
内部の辞書を上書き: {'datas': {'data_a': 4}}
settingsを上書き: {'datas': {'data_a': 1}}
settingsの浅いコピーを変更: {'datas': {'data_a': 6}}
settingsの浅いコピーに新しいキーを追加: {'datas': {'data_a': 1}}
settingsのdeepcopyを変更: {'datas': {'data_a': 1}}
updateで中身変更: {'datas': {'data_a': 9}}
updateで作った辞書で上書き: {'datas': {'data_a': 10}}

--- イミュータブルなオブジェクト (数値) の場合 ---
初期値: 1
数値を上書きしたつもり: 1

--- イミュータブルなオブジェクト (文字列) の場合 ---
初期値: ABC
文字列を上書きしたつもり: ABC

--- イミュータブルなオブジェクト (タプル) の場合 ---
初期値: (1, 2, 3)
タプルの直接変更を試みましたがエラー: 'tuple' object does not support item assignment
連結後のタプル: (1, 100, 3)
連結後のタプル(引数への変更なし): (1, 2, 3)
リスト変換後のタプル: (1, 200, 3)
リスト変換後のタプル(引数への変更なし): (1, 2, 3)
```
copy,deepcopyの挙動についてはコメント欄にも有用な情報を書き込んで頂けたのでご確認願います。