Python リストのcopy()|リストを複製する基本的な方法
Python初心者向けにlist.copy()メソッドの使い方を詳しく解説。浅いコピーと深いコピーの違い、複製方法の比較、実用的な活用例を実例で説明します。
Pythonでリストをコピーするとき、困ったことはありませんか?
みなさん、Pythonでプログラミングをしていると、既存のリストを元に新しいリストを作りたい場面が頻繁にありますよね。
「リストをコピーしたつもりなのに、元のリストも変わってしまった」
「copy()
って何?どう使うの?」
「浅いコピーと深いコピーの違いが分からない」
このような経験をしたことはありませんか?
実は、Pythonでリストを正しくコピーするには適切な方法があります。 単純な代入では元のリストと同じオブジェクトを参照してしまうため、意図しない問題が起こるんです。
この記事では、Python初心者の方向けにリストの複製方法について詳しく解説します。
copy()
メソッドの使い方から実用的な活用例まで、一緒に学んでいきましょう!
リストのcopy()メソッドとは?
copy()
メソッドは、既存のリストの浅いコピーを作成するリストの組み込みメソッドです。
簡単に言うと、元のリストとは独立した新しいリストを作ってくれるんです。
基本的な構文
基本的な使い方はとてもシンプルです。
新しいリスト = 元のリスト.copy()
たったこれだけで、元のリストと同じ内容の新しいリストが作成されます。 新しいリストオブジェクトが作成されるので、コピー後のリストを変更しても元のリストには影響しません。
これで安心してリストの操作ができますね!
代入とコピーの違い
まず、単純な代入とcopy()メソッドの違いを理解しましょう。
この違いを知らないと、思わぬバグを作ってしまうかもしれません。
単純な代入の問題
単純な代入では、実は新しいリストは作られません。
# 元のリストoriginal_list = [1, 2, 3, 4, 5]print(f"元のリスト: {original_list}")
# 単純な代入(参照のコピー)assigned_list = original_listprint(f"代入したリスト: {assigned_list}")
# 一つのリストを変更すると、もう一方も変わるassigned_list.append(6)print(f"代入リストに6を追加後:")print(f" 元のリスト: {original_list}") # [1, 2, 3, 4, 5, 6]print(f" 代入したリスト: {assigned_list}") # [1, 2, 3, 4, 5, 6]
# 同じオブジェクトかチェックprint(f"同じオブジェクト: {original_list is assigned_list}") # True
このコードでは、単純な代入の問題を示しています。
assigned_list
に6を追加したのに、original_list
も変更されてしまいました。
実行結果:
元のリスト: [1, 2, 3, 4, 5]
代入したリスト: [1, 2, 3, 4, 5]
代入リストに6を追加後:
元のリスト: [1, 2, 3, 4, 5, 6]
代入したリスト: [1, 2, 3, 4, 5, 6]
同じオブジェクト: True
これはassigned_list
とoriginal_list
が同じオブジェクトを参照しているからです。
copy()メソッドの使用
今度はcopy()
メソッドを使ってみましょう。
# 元のリストoriginal_list = [1, 2, 3, 4, 5]print(f"元のリスト: {original_list}")
# copy()メソッドでコピーcopied_list = original_list.copy()print(f"コピーしたリスト: {copied_list}")
# コピーしたリストを変更copied_list.append(6)print(f"コピーリストに6を追加後:")print(f" 元のリスト: {original_list}") # [1, 2, 3, 4, 5] (変更されない)print(f" コピーしたリスト: {copied_list}") # [1, 2, 3, 4, 5, 6]
# 異なるオブジェクトかチェックprint(f"異なるオブジェクト: {original_list is not copied_list}") # True
このコードでは、copy()
メソッドを使った正しいコピー方法を示しています。
今度は元のリストが変更されていませんね。
実行結果:
元のリスト: [1, 2, 3, 4, 5]
コピーしたリスト: [1, 2, 3, 4, 5]
コピーリストに6を追加後:
元のリスト: [1, 2, 3, 4, 5]
コピーしたリスト: [1, 2, 3, 4, 5, 6]
異なるオブジェクト: True
copy()
メソッドを使うことで、独立した新しいリストが作成されました。
リストをコピーする様々な方法
実は、Pythonにはリストをコピーする方法がいくつかあります。
それぞれの特徴を見ていきましょう。
copy()メソッド(推奨)
最も分かりやすくて推奨される方法です。
original = [1, 2, 3, 4, 5]
# copy()メソッドcopied = original.copy()print(f"copy()メソッド: {copied}")
# 変更のテストcopied[0] = 99print(f"変更後 - 元: {original}, コピー: {copied}")
このコードでは、copy()
メソッドの使用例を示しています。
copied[0]
を99に変更しても、元のリストは変わりません。
実行結果:
copy()メソッド: [1, 2, 3, 4, 5]
変更後 - 元: [1, 2, 3, 4, 5], コピー: [99, 2, 3, 4, 5]
意図通りに動作していますね。
スライス記法
Pythonらしい書き方の一つです。
original = [1, 2, 3, 4, 5]
# スライス記法でコピーcopied = original[:]print(f"スライス記法: {copied}")
# 変更のテストcopied[1] = 88print(f"変更後 - 元: {original}, コピー: {copied}")
このコードでは、スライス記法[:]
を使ったコピー方法を示しています。
全ての要素を取得することで、実質的にコピーになります。
実行結果:
スライス記法: [1, 2, 3, 4, 5]
変更後 - 元: [1, 2, 3, 4, 5], コピー: [1, 88, 3, 4, 5]
list()コンストラクタ
リストのコンストラクタを使う方法もあります。
original = [1, 2, 3, 4, 5]
# list()コンストラクタでコピーcopied = list(original)print(f"list()コンストラクタ: {copied}")
# 変更のテストcopied[2] = 77print(f"変更後 - 元: {original}, コピー: {copied}")
このコードでは、list()
コンストラクタを使ったコピー方法を示しています。
既存のリストから新しいリストを作成します。
実行結果:
list()コンストラクタ: [1, 2, 3, 4, 5]
変更後 - 元: [1, 2, 3, 4, 5], コピー: [1, 2, 77, 4, 5]
リスト内包表記
少し応用的な方法です。
original = [1, 2, 3, 4, 5]
# リスト内包表記でコピーcopied = [x for x in original]print(f"リスト内包表記: {copied}")
# 変更のテストcopied[3] = 66print(f"変更後 - 元: {original}, コピー: {copied}")
このコードでは、リスト内包表記を使ったコピー方法を示しています。
すべての要素x
を新しいリストに含めることでコピーを作成します。
実行結果:
リスト内包表記: [1, 2, 3, 4, 5]
変更後 - 元: [1, 2, 3, 4, 5], コピー: [1, 2, 3, 66, 5]
どの方法も正しくコピーできていますが、copy()
メソッドが最も分かりやすくておすすめです。
浅いコピーと深いコピー
ここからは少し応用的な内容です。
リストの中にリストがある場合、copy()
メソッドには注意が必要なんです。
浅いコピーの制限
copy()
メソッドは浅いコピーを作成します。
# ネストしたリスト(リストの中にリストがある)original = [[1, 2], [3, 4], [5, 6]]print(f"元のリスト: {original}")
# 浅いコピーshallow_copy = original.copy()print(f"浅いコピー: {shallow_copy}")
# 内側のリストを変更shallow_copy[0][0] = 99print(f"内側リスト変更後:")print(f" 元のリスト: {original}") # [[99, 2], [3, 4], [5, 6]] (影響あり)print(f" 浅いコピー: {shallow_copy}") # [[99, 2], [3, 4], [5, 6]]
# 外側のリストを変更shallow_copy.append([7, 8])print(f"外側リスト変更後:")print(f" 元のリスト: {original}") # [[99, 2], [3, 4], [5, 6]] (影響なし)print(f" 浅いコピー: {shallow_copy}") # [[99, 2], [3, 4], [5, 6], [7, 8]]
このコードでは、浅いコピーの制限を示しています。 内側のリストを変更すると、元のリストにも影響してしまいます。
実行結果:
元のリスト: [[1, 2], [3, 4], [5, 6]]
浅いコピー: [[1, 2], [3, 4], [5, 6]]
内側リスト変更後:
元のリスト: [[99, 2], [3, 4], [5, 6]]
浅いコピー: [[99, 2], [3, 4], [5, 6]]
外側リスト変更後:
元のリスト: [[99, 2], [3, 4], [5, 6]]
浅いコピー: [[99, 2], [3, 4], [5, 6], [7, 8]]
内側のリストは同じオブジェクトを参照しているため、変更が影響してしまいます。
深いコピーの使用
完全に独立したコピーが必要な場合は、深いコピーを使います。
import copy
# ネストしたリストoriginal = [[1, 2], [3, 4], [5, 6]]print(f"元のリスト: {original}")
# 深いコピーdeep_copy = copy.deepcopy(original)print(f"深いコピー: {deep_copy}")
# 内側のリストを変更deep_copy[0][0] = 99print(f"内側リスト変更後:")print(f" 元のリスト: {original}") # [[1, 2], [3, 4], [5, 6]] (影響なし)print(f" 深いコピー: {deep_copy}") # [[99, 2], [3, 4], [5, 6]]
# 外側のリストを変更deep_copy.append([7, 8])print(f"外側リスト変更後:")print(f" 元のリスト: {original}") # [[1, 2], [3, 4], [5, 6]] (影響なし)print(f" 深いコピー: {deep_copy}") # [[99, 2], [3, 4], [5, 6], [7, 8]]
このコードでは、copy.deepcopy()
を使った深いコピーを示しています。
内側のリストを変更しても、元のリストに影響しません。
実行結果:
元のリスト: [[1, 2], [3, 4], [5, 6]]
深いコピー: [[1, 2], [3, 4], [5, 6]]
内側リスト変更後:
元のリスト: [[1, 2], [3, 4], [5, 6]]
深いコピー: [[99, 2], [3, 4], [5, 6]]
外側リスト変更後:
元のリスト: [[1, 2], [3, 4], [5, 6]]
深いコピー: [[99, 2], [3, 4], [5, 6], [7, 8]]
深いコピーなら、完全に独立したコピーが作成されます。
複雑なオブジェクトでの比較
辞書を含むリストでも同様の現象が起こります。
import copy
# 辞書を含むリストoriginal = [ {"name": "田中", "scores": [85, 90, 88]}, {"name": "佐藤", "scores": [92, 87, 91]}, {"name": "鈴木", "scores": [78, 83, 85]}]
print("元のデータ:")for student in original: print(f" {student}")
# 浅いコピーshallow = original.copy()# 深いコピーdeep = copy.deepcopy(original)
# 浅いコピーの辞書を変更shallow[0]["name"] = "田中太郎"shallow[0]["scores"].append(95)
print(f"浅いコピー変更後:")print(f"元のデータの1番目: {original[0]}")print(f"浅いコピーの1番目: {shallow[0]}")
# 深いコピーの辞書を変更deep[1]["name"] = "佐藤花子"deep[1]["scores"].append(89)
print(f"深いコピー変更後:")print(f"元のデータの2番目: {original[1]}")print(f"深いコピーの2番目: {deep[1]}")
このコードでは、辞書を含むリストでの浅いコピーと深いコピーの違いを示しています。 辞書の内容を変更した場合の影響を確認できます。
実行結果:
元のデータ:
{'name': '田中', 'scores': [85, 90, 88]}
{'name': '佐藤', 'scores': [92, 87, 91]}
{'name': '鈴木', 'scores': [78, 83, 85]}
浅いコピー変更後:
元のデータの1番目: {'name': '田中太郎', 'scores': [85, 90, 88, 95]}
浅いコピーの1番目: {'name': '田中太郎', 'scores': [85, 90, 88, 95]}
深いコピー変更後:
元のデータの2番目: {'name': '佐藤', 'scores': [92, 87, 91]}
深いコピーの2番目: {'name': '佐藤花子', 'scores': [92, 87, 91, 89]}
浅いコピーでは辞書の内容も共有されてしまいますが、深いコピーでは完全に独立しています。
実用的な活用例
実際のプログラミングでのcopy()
メソッドの使い方を見てみましょう。
これらの例を参考に、自分のプロジェクトでも活用してみてください。
リストの安全な変更
関数でリストを処理するとき、元のリストを変更したくない場合があります。
def filter_and_sort_scores(student_scores, passing_score=60): """ スコアをフィルタリングして並び替え 元のリストは変更しない """ # 元のリストをコピーして安全に操作 filtered_scores = student_scores.copy() # 合格点以上のスコアのみを抽出 filtered_scores = [score for score in filtered_scores if score >= passing_score] # 降順で並び替え filtered_scores.sort(reverse=True) return filtered_scores
# 使用例all_scores = [85, 45, 92, 38, 78, 95, 52, 88, 41, 90]print(f"全スコア: {all_scores}")
# フィルタリング(元のリストは変更されない)passing_scores = filter_and_sort_scores(all_scores, 60)print(f"合格スコア(60点以上): {passing_scores}")
# 元のリストが変更されていないことを確認print(f"元のリスト(変更なし): {all_scores}")
# 異なる基準でもフィルタリングexcellent_scores = filter_and_sort_scores(all_scores, 80)print(f"優秀スコア(80点以上): {excellent_scores}")
このコードでは、元のリストを変更せずに安全に処理する方法を示しています。
copy()
メソッドを使うことで、元のデータを保護できます。
実行結果:
全スコア: [85, 45, 92, 38, 78, 95, 52, 88, 41, 90]
合格スコア(60点以上): [95, 92, 90, 88, 85, 78]
元のリスト(変更なし): [85, 45, 92, 38, 78, 95, 52, 88, 41, 90]
優秀スコア(80点以上): [95, 92, 90, 88, 85]
元のリストが変更されずに、異なる条件でフィルタリングできています。
デフォルト設定の管理
設定を管理するクラスでの活用例です。
class GameSettings: def __init__(self): # デフォルト設定 self.default_config = { "difficulty": "normal", "sound_volume": 80, "music_volume": 60, "graphics_quality": "high", "controls": ["W", "A", "S", "D"] } def get_config(self, custom_settings=None): """ 設定を取得(デフォルト設定をベースにカスタム設定を適用) """ # デフォルト設定をコピー config = self.default_config.copy() # カスタム設定があれば適用 if custom_settings: config.update(custom_settings) return config def reset_to_default(self): """デフォルト設定に戻す""" return self.default_config.copy()
# 使用例game = GameSettings()
# デフォルト設定default_config = game.get_config()print("デフォルト設定:")for key, value in default_config.items(): print(f" {key}: {value}")
# カスタム設定custom_settings = { "difficulty": "hard", "sound_volume": 100, "controls": ["↑", "←", "↓", "→"]}
custom_config = game.get_config(custom_settings)print(f"カスタム設定:")for key, value in custom_config.items(): print(f" {key}: {value}")
# デフォルト設定が変更されていないことを確認print(f"デフォルト設定(変更なし):")for key, value in game.default_config.items(): print(f" {key}: {value}")
このコードでは、ゲーム設定の管理例を示しています。 デフォルト設定をコピーして使うことで、元の設定を保護できます。
実行結果:
デフォルト設定:
difficulty: normal
sound_volume: 80
music_volume: 60
graphics_quality: high
controls: ['W', 'A', 'S', 'D']
カスタム設定:
difficulty: hard
sound_volume: 100
music_volume: 60
graphics_quality: high
controls: ['↑', '←', '↓', '→']
デフォルト設定(変更なし):
difficulty: normal
sound_volume: 80
music_volume: 60
graphics_quality: high
controls: ['W', 'A', 'S', 'D']
カスタム設定を適用してもデフォルト設定が変更されていません。
データの段階的処理
データを段階的に処理しながら、各段階の結果を保持する例です。
def process_sales_data(raw_data): """ 売上データを段階的に処理 各段階でのデータ状態を保持 """ print("=== 売上データ処理 ===") print(f"元データ: {raw_data}") # ステップ1: データクリーニング(負の値を除去) step1_data = raw_data.copy() step1_data = [x for x in step1_data if x >= 0] print(f"ステップ1(負の値除去): {step1_data}") # ステップ2: 外れ値除去(平均の3倍以上を除去) if step1_data: mean_value = sum(step1_data) / len(step1_data) step2_data = step1_data.copy() step2_data = [x for x in step2_data if x <= mean_value * 3] print(f"ステップ2(外れ値除去、平均: {mean_value:.1f}): {step2_data}") else: step2_data = [] # ステップ3: 並び替え step3_data = step2_data.copy() step3_data.sort(reverse=True) print(f"ステップ3(降順並び替え): {step3_data}") # ステップ4: 上位5件を抽出 step4_data = step3_data.copy() step4_data = step4_data[:5] print(f"ステップ4(上位5件): {step4_data}") return { "original": raw_data, "cleaned": step1_data, "outliers_removed": step2_data, "sorted": step3_data, "top_5": step4_data }
# 使用例sales_data = [1200, -50, 3500, 980, 15000, 2200, 1800, 750, 4200, 1100, 2800]results = process_sales_data(sales_data)
print(f"=== 処理結果サマリー ===")print(f"元データ件数: {len(results['original'])}")print(f"クリーニング後: {len(results['cleaned'])}")print(f"外れ値除去後: {len(results['outliers_removed'])}")print(f"最終上位5件: {results['top_5']}")
このコードでは、データを段階的に処理する例を示しています。
各段階でcopy()
を使うことで、前の段階のデータを保持できます。
実行結果:
=== 売上データ処理 ===
元データ: [1200, -50, 3500, 980, 15000, 2200, 1800, 750, 4200, 1100, 2800]
ステップ1(負の値除去): [1200, 3500, 980, 15000, 2200, 1800, 750, 4200, 1100, 2800]
ステップ2(外れ値除去、平均: 3353.0): [1200, 3500, 980, 2200, 1800, 750, 4200, 1100, 2800]
ステップ3(降順並び替え): [4200, 3500, 2800, 2200, 1800, 1200, 1100, 980, 750]
ステップ4(上位5件): [4200, 3500, 2800, 2200, 1800]
=== 処理結果サマリー ===
元データ件数: 11
クリーニング後: 10
外れ値除去後: 9
最終上位5件: [4200, 3500, 2800, 2200, 1800]
各段階の処理結果がきちんと保持されています。
エラーハンドリングとベストプラクティス
最後に、安全で効率的なコピー処理のコツをお教えします。
これらを知っておくと、より良いプログラムが書けるようになります。
安全なコピー関数
型チェック付きの安全なコピー関数を作ってみましょう。
def safe_copy(obj): """ 安全なコピー関数(型チェック付き) """ if obj is None: return None if isinstance(obj, list): return obj.copy() elif isinstance(obj, dict): return obj.copy() elif isinstance(obj, set): return obj.copy() elif isinstance(obj, tuple): return obj # タプルは不変なのでそのまま返す else: # その他の型はcopyモジュールを使用 import copy try: return copy.copy(obj) except Exception as e: print(f"コピーできません: {e}") return obj
# テストtest_objects = [ [1, 2, 3], # リスト {"a": 1, "b": 2}, # 辞書 {1, 2, 3}, # セット (1, 2, 3), # タプル "文字列", # 文字列 42, # 数値 None # None]
for obj in test_objects: copied = safe_copy(obj) print(f"{type(obj).__name__}: {obj} -> {copied} (同じ: {obj is copied})")
このコードでは、さまざまな型に対応した安全なコピー関数を示しています。 型に応じて適切なコピー方法を選択します。
実行結果:
list: [1, 2, 3] -> [1, 2, 3] (同じ: False)
dict: {'a': 1, 'b': 2} -> {'a': 1, 'b': 2} (同じ: False)
set: {1, 2, 3} -> {1, 2, 3} (同じ: False)
tuple: (1, 2, 3) -> (1, 2, 3) (同じ: True)
str: 文字列 -> 文字列 (同じ: True)
int: 42 -> 42 (同じ: True)
NoneType: None -> None (同じ: True)
リスト、辞書、セットは新しいオブジェクトが作られ、不変オブジェクトは同じオブジェクトが返されています。
コピー検証関数
コピーが正しく行われたかを検証する関数も作ってみましょう。
def verify_copy(original, copied): """ コピーが正しく行われたかを検証 """ checks = { "内容が同じ": original == copied, "異なるオブジェクト": original is not copied, "同じ型": type(original) == type(copied), "同じサイズ": len(original) == len(copied) if hasattr(original, '__len__') else True } print("=== コピー検証結果 ===") all_passed = True for check_name, result in checks.items(): status = "✓" if result else "✗" print(f"{status} {check_name}: {result}") if not result: all_passed = False return all_passed
# 使用例original_list = [1, 2, 3, [4, 5]]copied_list = original_list.copy()
print(f"元のリスト: {original_list}")print(f"コピー: {copied_list}")verify_copy(original_list, copied_list)
# ネストしたリストの問題を示すcopied_list[3][0] = 99print(f"ネスト要素変更後:")print(f"元のリスト: {original_list}") # [1, 2, 3, [99, 5]] - 影響ありprint(f"コピー: {copied_list}") # [1, 2, 3, [99, 5]]
このコードでは、コピーの検証方法を示しています。 ネストしたリストの問題も確認できます。
実行結果:
元のリスト: [1, 2, 3, [4, 5]]
コピー: [1, 2, 3, [4, 5]]
=== コピー検証結果 ===
✓ 内容が同じ: True
✓ 異なるオブジェクト: True
✓ 同じ型: True
✓ 同じサイズ: True
ネスト要素変更後:
元のリスト: [1, 2, 3, [99, 5]]
コピー: [1, 2, 3, [99, 5]]
基本的なコピーは成功していますが、ネストした要素の変更は両方に影響しています。
まとめ
リストのcopy()
メソッドは、Pythonでリストを安全に複製する最も基本的で重要な方法です。
この記事で学んだポイントを振り返ってみましょう。
copy()メソッドの特徴
主な特徴をまとめると以下のようになります。
- 独立性: 元のリストとは独立したオブジェクトを作成
- 浅いコピー: 第一レベルの要素のみをコピー
- 効率性: 内部的に最適化された高速な処理
- 安全性: 元のリストを変更するリスクを回避
主な使用場面
実際のプログラミングでは、こんな場面で活用できます。
- データ処理: 元データを保持しながら加工
- 設定管理: デフォルト設定をベースにしたカスタマイズ
- 状態管理: 変更履歴やロールバック機能
- 関数設計: 引数のリストを安全に操作
ベストプラクティス
より良いプログラムを書くためのコツです。
- 適切な方法選択: 浅いコピーで十分か深いコピーが必要か判断
- パフォーマンス考慮: 大きなリストではメモリ使用量に注意
- 型チェック: コピー対象がリストかどうか確認
- テスト: コピー後の独立性を必ず検証
リストの複製を適切に行うことで、より安全で保守しやすいPythonプログラムを作成できるようになります。
まずは基本的なcopy()
メソッドから始めて、必要に応じて深いコピーも活用してみてください。
きっとプログラミングがもっと楽しくなりますよ!