この単位では、Pythonにおける代入、参照、コピーの基本を扱う。
- 変数への代入の意味
- 参照の共有
- 再束縛
isと参照の関係- 浅いコピー
- 深いコピー
copy.copycopy.deepcopy
この単位の主な論点は次の通り。
- 変数への代入は、値そのものを常にコピーする操作ではない
- 変数はオブジェクトに名前を付けるものとして考えると理解しやすい
- 再束縛は、変数名を別のオブジェクトに結び直す操作である
- 複数の変数が同じミュータブルな値を指すと、変更が共有される
==は値として等しいかを確認するisは同じオブジェクトを指しているかを確認する- 浅いコピーは外側のコレクションだけを新しく作る
- 深いコピーは内側のミュータブルな値も再帰的にコピーする
この単位のファイル構成は次の通り。
src/04_assignment_references_and_copying/
main.py
assignment_and_rebinding.py
shared_references.py
identity_and_equality.py
shallow_copy_examples.py
deep_copy_examples.py
各ファイルの役割は次の通り。
main.py- Unit 04 の実行入口
- 各テーマ別ファイルの関数を順番に呼び出す
assignment_and_rebinding.py- 変数への代入、再束縛、ミュータブルな値の変更を扱う
shared_references.py- 同じオブジェクトを複数の変数から参照する例を扱う
identity_and_equality.pyisと==の違い、None判定を扱う
shallow_copy_examples.py- スライス、
list()、copy.copy()、dict.copy()による浅いコピーを扱う
- スライス、
deep_copy_examples.pycopy.deepcopy()による深いコピーと浅いコピーの違いを扱う
リポジトリ直下で仮想環境を有効化してから実行する。
PowerShell の場合:
.venv\Scripts\Activate.ps1
python src/04_assignment_references_and_copying/main.pyGit Bash の場合:
source .venv/Scripts/activate
python src/04_assignment_references_and_copying/main.pyRuff の確認は次のコマンドで行う。
python -m ruff check .
python -m ruff format --check .必要に応じてフォーマットを実行する。
python -m ruff format .次の順番で読むと、内容を追いやすい。
main.pyassignment_and_rebinding.pyshared_references.pyidentity_and_equality.pyshallow_copy_examples.pydeep_copy_examples.py
最初に main.py を読むことで、この単位全体の実行順序を把握できる。
その後、代入と再束縛、参照の共有、同一性、浅いコピー、深いコピーの順番で読む。
Unit 04 全体の処理の流れは次の通り。
main.pyが実行されるmain()が呼び出される- 表示用の見出しを出す
- 代入と再束縛のサンプルを実行する
- 参照の共有のサンプルを実行する
isと==の違いを確認する- 浅いコピーのサンプルを実行する
- 深いコピーのサンプルを実行する
- 各ファイル内の
assertにより、軽い期待値確認を行う
この単位では、変数名とオブジェクトの関係を主題にしている。
各ファイルの run_...() 関数は、テーマ別サンプルをまとめて実行するための入口として使う。
assignment_and_rebinding.py では、文字列を使って再束縛を確認する。
language = "Python"
same_language = language
same_language = "Java"最初の same_language = language では、same_language は language と同じ文字列を指す。
その後の same_language = "Java" は、same_language という名前を別の文字列に結び直している。
この操作は、language の値そのものを変更しているわけではない。
そのため、再束縛後も language は "Python" のままとなる。
assignment_and_rebinding.py では、list に対する変更を扱う。
scores = [80, 90]
same_scores = scores
same_scores.append(100)scores と same_scores は同じ list を指している。
そのため、same_scores.append(100) を行うと、scores から見ても値が追加されている。
代入は自動的に list の中身をコピーしない。
この感覚は、Pythonのコレクションを扱う上で重要となる。
identity_and_equality.py では、同じ値を持つ2つの list を比較する。
first_numbers = [1, 2, 3]
second_numbers = [1, 2, 3]
shared_numbers = first_numbersfirst_numbers == second_numbers は True になる。
これは、2つの list が値として等しいためである。
一方、first_numbers is second_numbers は False になる。
別々に作成した list なので、同じオブジェクトではないためである。
shallow_copy_examples.py では、ネストした list の浅いコピーを扱う。
original_scores = [["Sora", 80], ["Mio", 90]]
copied_by_slice = original_scores[:]
copied_by_slice[0][1] = 100original_scores[:] は、外側の list を新しく作る。
ただし、内側の ["Sora", 80] まではコピーしない。
そのため、copied_by_slice[0][1] = 100 を行うと、元の original_scores 側にも変化が見える。
deep_copy_examples.py では、copy.deepcopy() を使う。
copied_user = copy.deepcopy(original_user)
copied_user["scores"].append(100)
copied_user["profile"]["level"] = "intermediate"copy.deepcopy() は、外側の dict だけでなく、内側の list や dict もコピーする。
そのため、コピー側の scores や profile を変更しても、元の値には影響しない。
ネストしたデータを安全に複製したい場合は、浅いコピーとの違いを意識する必要がある。
shared_references.py では、list を別の変数に代入する例を扱う。
users = ["Sora", "Mio"]
active_users = users
active_users.append("Ren")active_users = users は、users の中身をコピーしているわけではない。
active_users と users は、同じ list を指している。
そのため、active_users.append("Ren") を行うと、users 側から見ても "Ren" が追加されている。
identity_and_equality.py では、値の比較と同一性の比較を分けている。
first_numbers = [1, 2, 3]
second_numbers = [1, 2, 3]
first_numbers == second_numbers
first_numbers is second_numbers== は値として等しいかを見る。
is は同じオブジェクトを指しているかを見る。
値が同じかどうかを確認したい場合は、基本的に == を使う。
is は None 判定や同じオブジェクトかどうかを確認したい場面で使う。
shallow_copy_examples.py では、浅いコピー後に内側の list を変更している。
copied_by_slice = original_scores[:]
copied_by_slice[0][1] = 100copied_by_slice は外側の list としては original_scores とは別物である。
しかし、内側の list は共有されている。
そのため、ネストしたデータを扱うときは、浅いコピーだけで十分かを確認する必要がある。
shallow_copy_examples.py では、dict.copy() の例も扱う。
original_user = {
"name": "Sora",
"skills": ["Python", "SQL"],
}
copied_user = original_user.copy()
copied_user["skills"].append("Git")original_user.copy() は、新しい dict を作る。
ただし、値として入っている skills の list は共有されたままとなる。
そのため、copied_user["skills"].append("Git") を行うと、original_user の skills にも "Git" が追加される。
deep_copy_examples.py では、浅いコピーと深いコピーの違いを比較している。
copied_user = copy.deepcopy(original_user)
shallow_user = copy.copy(original_user)copy.deepcopy() は、ネストした値もコピーするため安全に見える。
ただし、データが大きい場合や複雑なオブジェクトを含む場合は、処理コストや意図しない挙動にも注意が必要となる。
今回の単位では、まず浅いコピーと深いコピーの違いを読めることを重視する。
この単位を読んだ後、次の内容を確認する。
- 変数への代入が常にコピーではないことを説明できる
- 再束縛とミュータブルな値の変更の違いを説明できる
- 複数の変数が同じ
listを指す例を読める ==とisの違いを説明できるNone判定でis Noneを使う理由を説明できる- 浅いコピーが外側だけを新しく作ることを説明できる
- ネストした値では浅いコピーの影響が残ることを説明できる
copy.copy()とdict.copy()が浅いコピーであることを説明できるcopy.deepcopy()がネストした値もコピーすることを説明できる