Pythonでリストをコピーする方法|copy()・スライス・list()の違いと、一部だけ・2次元リストのコピー

Pythonでリストをコピーするなら、基本は b = a.copy() です。a[:]・list(a) でも同じ浅いコピーができますが、b = a はコピーではなく同じリストに別名を付けるだけなので、片方を変えるともう片方も変わります。

この記事は「リストのコピー」の書き方に絞って、5通りの書き方の違いと速さ、一部だけコピーする方法、ループや関数の中でコピーを忘れたときに起きること、2次元リストのコピーまでを、実際に動かした結果つきで説明します。浅いコピーと深いコピーの考え方そのものは「Pythonの浅いコピーと深いコピーの違い」で詳しく扱っています。

実行環境はPython 3.12.15(Apple M2のMac)です。ここで使う書き方はPython 3.3以降ならどれも同じように動きます(2026年10月18日確認時点の最新版はPython 3.15.0)。

代入(b = a)はコピーではない

最初に、コピーが必要になる理由を確かめます。

import copy
a = [1, 2, 3]
b = a
b.append(4)
print(a)
print(a is b)

実行結果:

[1, 2, 3, 4]
True

bに追加したのにaも変わりました。a is bがTrueなので、2つの名前が同じリストを指しています。別のリストとして編集したいときは、次の章のどれかで新しいリストを作ります。なぜこうなるのかは「変数に代入したら元の変数まで変わる原因と対策」で解説しています。

リストをコピーする5つの書き方

import copy

a = [1, 2, 3]

b1 = a.copy()
b2 = a[:]
b3 = list(a)
b4 = [*a]
b5 = copy.copy(a)

for b in (b1, b2, b3, b4, b5):
    print(b, b == a, b is a)

実行結果:

[1, 2, 3] True False
[1, 2, 3] True False
[1, 2, 3] True False
[1, 2, 3] True False
[1, 2, 3] True False

どれも中身は同じ(==がTrue)で、元とは別のリスト(isがFalse)です。5つとも浅いコピーで、結果に違いはありません。公式ドキュメントでも、s.copy()はs[:]と同じ浅いコピーだと説明されています(組み込み型)。

書き方 特徴 向いている場面
a.copy() 名前で「コピー」と分かる。Python 3.3で追加 迷ったらこれ
a[:] 古いコードでよく見る。一部だけのコピーにも同じ形で使える 範囲を指定したいとき
list(a) リスト以外(タプル・range・集合など)からも作れる 元がリストとは限らないとき
[*a] ほかの要素とまとめて新しいリストを作れる([0, *a, 99]) 前後に要素を足しながらコピー
copy.copy(a) リストを継承した独自クラスでも型を保つ 型を変えたくないとき

独自のリストクラスでは結果の型が変わる

5つの違いが出るのは、listを継承したクラスをコピーするときだけです。

import copy

class MyList(list):
    pass

m = MyList([1, 2, 3])
print(type(m.copy()).__name__)
print(type(m[:]).__name__)
print(type(copy.copy(m)).__name__)

実行結果:

list
list
MyList

copy()とスライスは普通のlistを返し、copy.copy()だけが元の型を保ちます。これもcopyモジュールのドキュメントに書かれている動きです。

速さの違い

1,000要素のリストを10万回コピーして、5回測った中の最短時間を比べました。

import copy
import timeit

a = list(range(1000))
tests = {
    "a.copy()": lambda: a.copy(),
    "a[:]": lambda: a[:],
    "list(a)": lambda: list(a),
    "[*a]": lambda: [*a],
    "copy.copy(a)": lambda: copy.copy(a),
    "[x for x in a]": lambda: [x for x in a],
}
for name, f in tests.items():
    t = min(timeit.repeat(f, number=100_000, repeat=5))
    print(f"{name:16} {t:.3f}秒")

実行結果:

a.copy()         0.108秒
a[:]             0.107秒
list(a)          0.112秒
[*a]             0.112秒
copy.copy(a)     0.117秒
[x for x in a]   0.822秒

copy()・スライス・list()・[*a]はほぼ同じで、copy.copy()は関数を経由するぶん少しだけ遅くなりました。数値は環境で変わりますが、この4つの差は気にしなくてよい程度です。一方、内包表記[x for x in a]は1要素ずつ処理するので7倍以上かかっています。単にコピーするだけなら内包表記は使わないほうがよいでしょう。

リストの一部だけをコピーする(スライス)

「先頭の3件だけ」「後ろの2件だけ」のように一部を取り出すときは、スライスを使います。スライスで取り出したものも新しいリストです。

a = [10, 20, 30, 40, 50, 60]
print(a[1:4])
print(a[:3])
print(a[-2:])
print(a[::2])
part = a[1:4]
part[0] = 999
print(a)

実行結果:

[20, 30, 40]
[10, 20, 30]
[50, 60]
[10, 30, 50]
[10, 20, 30, 40, 50, 60]

取り出したpartを書き換えても、元のaは変わっていません。a[開始:終了]の終了の位置は含まれない点と、a[::2]のように間隔も指定できる点を覚えておくと便利です。

既にあるリストの中身を入れ替える(b[:] = a)

ほかの変数からも参照されているリストの中身だけを入れ替えたいときは、b = a.copy()ではなくb[:] = aと書きます。

a = [1, 2, 3]
b = [9, 9]
alias = b

b[:] = a
print(b, alias, b is alias)

b = a.copy()
print(b, alias, b is alias)

実行結果:

[1, 2, 3] [1, 2, 3] True
[1, 2, 3] [1, 2, 3] False

b[:] = aはbそのものの中身を書き換えるので、同じリストを指していたaliasにも新しい中身が見えます。b = a.copy()はbという名前を新しいリストに付け替えるだけなので、aliasは古いリストを指したままです。関数の中で引数のリストを丸ごと入れ替えたいときにこの違いが効いてきます。

コピーを忘れたときに起きる3つの失敗

1. ループでappendすると全部同じ中身になる

row = []
table = []
for i in range(3):
    row.append(i)
    table.append(row)
print(table)

row = []
table = []
for i in range(3):
    row.append(i)
    table.append(row.copy())
print(table)

実行結果:

[[0, 1, 2], [0, 1, 2], [0, 1, 2]]
[[0], [0, 1], [0, 1, 2]]

1つ目は、同じrowを3回追加しているので、最後の状態が3つ並びます。その時点の中身を残したいなら、追加するときにrow.copy()を渡します。

2. 関数の中で引数のリストを書き換えてしまう

def add_tax(prices):
    prices = prices.copy()
    for i in range(len(prices)):
        prices[i] = int(prices[i] * 1.1)
    return prices

original = [100, 200, 300]
print(add_tax(original))
print(original)

nums = [3, 1, 2]
print(sorted(nums))
print(nums)
nums.sort()
print(nums)

実行結果:

[110, 220, 330]
[100, 200, 300]
[1, 2, 3]
[3, 1, 2]
[1, 2, 3]

関数の最初でprices = prices.copy()としておけば、呼び出し側のリストは変わりません。並べ替えも同じで、sorted()は新しいリストを返し、.sort()は元のリストを書き換えます。

3. 2次元リストをcopy()すると中のリストは共有される

import copy
grid = [[0, 0], [0, 0]]

g1 = grid.copy()
g1[0][0] = 1
print(grid)

grid = [[0, 0], [0, 0]]
g2 = [row.copy() for row in grid]
g2[0][0] = 1
print(grid, g2)

g3 = copy.deepcopy(grid)
g3[1][1] = 5
print(grid, g3)

実行結果:

[[1, 0], [0, 0]]
[[0, 0], [0, 0]] [[1, 0], [0, 0]]
[[0, 0], [0, 0]] [[0, 0], [0, 5]]

grid.copy()は外側のリストだけを新しくするので、g1[0][0] = 1で元のgridまで変わりました。中の行まで別にしたいときは、[row.copy() for row in grid]かcopy.deepcopy()を使います。

2次元リストは「行ごとのcopy()」と「deepcopy()」のどちらを使うか

中身が数値や文字列だけの2次元リストなら、行ごとにcopy()すれば十分です。100×100のリストを1,000回コピーして比べました。

import copy
import timeit

grid = [[0] * 100 for _ in range(100)]
t1 = min(timeit.repeat(lambda: [row.copy() for row in grid], number=1000, repeat=5))
t2 = min(timeit.repeat(lambda: copy.deepcopy(grid), number=1000, repeat=5))
print(f"[row.copy() for row in grid]: {t1:.3f}秒")
print(f"copy.deepcopy(grid):          {t2:.3f}秒")

実行結果:

[row.copy() for row in grid]: 0.018秒
copy.deepcopy(grid):          1.360秒

この条件ではdeepcopy()のほうが数十倍遅くなりました。deepcopy()は要素を1つずつたどって、同じオブジェクトを2回コピーしないよう記録しながら進むためです。3段以上の入れ子や、リストの中に辞書やクラスのオブジェクトが入っているときはdeepcopy()を使い、数値だけの表なら行ごとのコピーで足ります。[[0] * 3] * 3で作ったリストがすでに行を共有している問題は「Pythonの2次元配列の作り方と使い方」で扱っています。

タプルや文字列はコピーしなくてよい

t = (1, 2, 3)
print(tuple(t) is t)
print(t[:] is t)
s = "abc"
print(s[:] is s)

実行結果:

True
True
True

タプルと文字列は中身を変えられない(イミュータブルな)型なので、コピーしても同じオブジェクトが返ってきます。書き換えられないものは共有しても困らないため、コピーを作る必要がありません。

辞書(dict)のコピーも書き方は同じ考え方

d = {"name": "田中", "tags": ["python"]}

d1 = d.copy()
d2 = dict(d)
d3 = {**d}
d4 = d | {}

d1["name"] = "佐藤"
print(d["name"], d1["name"])

d1["tags"].append("sql")
print(d["tags"])
print(d2["tags"] is d["tags"], d3["tags"] is d["tags"], d4["tags"] is d["tags"])

実行結果:

田中 佐藤
['python', 'sql']
True True True

辞書もcopy()・dict()・{**d}・d | {}(Python 3.9以降)でコピーでき、どれも浅いコピーです。キーに入れ直した値は元に影響しませんが、値がリストのときはそのリストを共有します。辞書の5つの書き方とdeepcopy()の使い分けは「Pythonの浅いコピーと深いコピーの違い」、copy()とdeepcopy()だけを比べたい場合は「Python copy()とdeepcopy()の違い」と「Python deepcopy()とは?」を見てください。

目的別の早見表

やりたいこと 書き方
リスト全体を別のリストとしてコピー b = a.copy()
一部だけコピー b = a[1:4]
タプルやrangeからリストを作る b = list(t)
前後に要素を足しながらコピー b = [0, *a, 99]
同じリストの中身だけ入れ替える b[:] = a
数値だけの2次元リスト b = [row.copy() for row in a]
入れ子が深い・中に辞書やオブジェクトがある b = copy.deepcopy(a)

テックジムで基礎から書いて覚える

参照とコピーのように「動かしてみないと腑に落ちない」部分は、自分で書いて結果を確かめるのが近道です。テックジムは対面・通学型のプログラミングスクールで、東京本校(永田町)では自分のペースで課題を進め、詰まったらトレーナーに質問できます。まずは無料講座から試せます。

よくある質問

Q. Pythonでリストをコピーする一番よい方法はどれですか?

a.copy()がおすすめです。名前でコピーだと分かり、速さもスライス(a[:])やlist(a)と変わりません。範囲を指定したいときはスライス、元がタプルなどのときはlist()を使います。

Q. b = a と b = a.copy() は何が違いますか?

b = a は同じリストに別名を付けるだけなので、bを変えるとaも変わります。b = a.copy() は新しいリストを作るので、bに追加や削除をしてもaは変わりません。

Q. copy()したのに元のリストが変わるのはなぜですか?

copy()は浅いコピーで、外側のリストだけを新しくするためです。中にリストや辞書が入っていると、その中身は元と共有されます。2次元リストなら [row.copy() for row in a]、入れ子が深いときは copy.deepcopy(a) を使います。

Q. リストの一部だけをコピーするにはどうしますか?

スライスを使います。a[1:4] で2番目から4番目まで、a[:3] で先頭3件、a[-2:] で末尾2件を新しいリストとして取り出せます。取り出したリストを書き換えても元のリストは変わりません。

Q. タプルや文字列もコピーが必要ですか?

必要ありません。タプルと文字列は中身を変更できない型なので、tuple(t) や s[:] としても同じオブジェクトが返ります。共有していても書き換えられないので問題は起きません。

対面・無料|東京・永田町で毎週土曜日13時

1回の参加で、プログラミングの構造がわかる

ゼロからはじめるPython入門講座

8年間・累計600回開催/これまで全国で2万人以上が参加

「やはり対面で聞けるのはとても気づきがあり、価値があると思います」

参加者の声

  • 本当に初心者からの講座です。プログラミングに触ったことがなくて大丈夫です
  • 対面だから、その場で聞けます。つまずいた所を持ち帰らずに解決できます
  • 講座は1時間。そのあとも残って学習を続けられ、講師にも質問できます
  • 独学で挫折した方も。糸口が見つかって、講座のあと会員になる方が多くいます

無料で参加する|開催日程を見る

参加費は無料です