Python copyメソッドとスライスの使い方【リストの複製】

Python
この記事は約10分で読めます。

こんにちは、ナナです。

同一のリスト要素を「複製」する方法を学んでいきましょう。この複製には注意が必要です。

次のようにリスト変数「list1」を別のリスト変数「list2」へ代入したとしましょう。

# リスト定義
list1 = [10, 20, 30]

# リストを別変数に代入し[0]の要素を書き換え
list2 = list1
list2[0] = 50

# リストを表示
print("リスト1:", list1)
print("リスト2:", list2)

「list2」 の[0]の要素を書き換えると、次のように「list1」の内容も変わってしまっていますね。つまり、複製ができていないということです。

リスト1: [50, 20, 30]
リスト2: [50, 20, 30]

本記事では次の疑問点を解消する内容となっています。

本記事で学習できること
  • リスト変数の代入でリストが複製できない理由とは?
  • リストオブジェクトを複製する方法とは?
  • copyメソッドの使い方とは?
  • スライス機能の使い方とは?

では、リストの複製方法について学んでいきましょう。

スポンサーリンク

リスト変数の代入では、なぜリストを複製できないのか?

ちょっと、どういうことですかっ!なんで、別の変数に入れてから書き換えたのに、元のリストまで変わっちゃうんですか・・・?

次のプログラムで確認してみましたよ。「数値」の場合は代入後に書き換えても、やっぱり元の変数の「数値」はそのままですよー。

# num1変数を定義
num1 = 100

# num2へ代入し書き換え
num2 = num1
num2 = 200

# 変数を表示
print("num1:", num1)
print("num2:", num2)
num1: 100
num2: 200
ナナ
ナナ

自分で確認するって偉いねー、そうなんだよ。言われたことを鵜呑みにするんじゃなくて、自分なりに実験してみるってすごく大事だよ。

この現象は「変数」と「オブジェクト」の関係性をしっかりと把握しておくことで理由がわかります。

この「リスト」の複製に関わる不可解な現象は、「オブジェクト」と「変数」の関係性をしっかりと把握することで理解できます。

変数同士の代入とは「参照」のコピーである

Pythonという言語においては、変数同士の代入とは「参照」をコピーすることになります。プログラムで確認してみましょう。

# リスト定義と変数への代入
list1 = [10, 20, 30]
list2 = list1

# 変数の参照先オブジェクトのIDを表示
print("list1:", id(list1))
print("list2:", id(list2))

動かした結果、次のように参照先オブジェクトのIDは同一であることがわかります。

list1: 2053492597256
list2: 2053492597256

これはイメージ図で表現すると、次のようになります。

同一のリストオブジェクトを参照

このように、「list1」と「list2」は同じリストオブジェクトを参照していることになります。

ここで、「list2」を使ってリスト要素の値を書き換えてみましょう!

# リスト定義と変数へのコピー
list1 = [10, 20, 30]
list2 = list1

# 変数の参照先オブジェクトのIDを表示
print("list1:", id(list1))
print("list2:", id(list2))

list2[0] = 50

そうすると、リストオブジェクトの要素[0]の参照先オブジェクトが「50」に変わります。

リストを書き換える問題

結果、「list1」と「list2」は同一のリストオブジェクトを参照しているため、「list1」からみたリストオブジェクトの中身も入れ替わってしまいました。

これが、このリスト書き換え問題の発生理由です。

ナナ
ナナ

このようにPythonにおける「変数」と「オブジェクト」の関係性をしっかりと理解していないと、意図せぬ書き換えが発生するため注意が必要なんです!

スポンサーリンク

リストオブジェクトを複製する方法

師匠!では、この問題はどうやって解決するのですかっ!わたしはリストを別の情報として管理したかったんです。

ナナ
ナナ

リストオブジェクトを別々で管理したいってことだね。

リストオブジェクトが1つしかないのが問題ですので、別のリストオブジェクトを複製すればいいってことです!

リストオブジェクトを複製する方法はいくつかあります。

copyメソッドを使ってリストオブジェクトを複製

まずは、リストオブジェクトで使える「copyメソッド」を使った複製方法を紹介しましょう。

list1 = [10, 20, 30]

# copyメソッドでリストオブジェクトを複製
list2 = list1.copy()

# 変数の参照先オブジェクトのIDを表示
print("list1:", list1, id(list1))
print("list2:", list2, id(list2))
list1: [10, 20, 30] 1346469978632
list2: [10, 20, 30] 1346469978696

このように、リスト内容は同じですが、リストオブジェクトのIDは異なっていますね。

copyメソッドの動作

copyメソッドを利用することで、リスト内容は同じですがオブジェクトは別のリストを作り出すことができます。

ナナ
ナナ

オブジェクトを別にした場合は、「list1」と「list2」それぞれでリスト要素を書き換えても、お互いに影響はありませんよ。

list関数を使ったリストオブジェクトの複製

list関数の引数にリストオブジェクトを与えると、同一内容のリストを作成します。

list1 = [10, 20, 30]

# list関数を使ったオブジェクトの複製
list2 = list(list1)

# 変数の参照先オブジェクトのIDを表示
print("list1:", list1, id(list1))
print("list2:", list2, id(list2))
list1: [10, 20, 30] 2013884670472
list2: [10, 20, 30] 2013887283848

これも結果からわかるように、別のリストオブジェクトが作成されていますね。

copyモジュールを使ったリストオブジェクトの複製

Pythonには「copyモジュール」と呼ばれるオブジェクトの複製機能があります。

「import copy」を書くことで、copyモジュールを使えるようにすれば、copy関数が利用できるようになります。

# copyモジュールのインポート
import copy

list1 = [10, 20, 30]

# copyモジュールのcopy関数使った複製
list2 = copy.copy(list1)

# 変数の参照先オブジェクトのIDを表示
print("list1:", list1, id(list1))
print("list2:", list2, id(list2))

リストに限らずオブジェクトを複製することができます。

スポンサーリンク

スライスを使ったリストオブジェクトの複製と一部複製

ここまでの「複製」ってリスト全体が同じものですよね。リストの一部分だけを「複製」することってできるんですか?

ナナ
ナナ

Pythonには「スライス」と呼ばれる機能があり、この「スライス」を使うことで、リストの一部をオブジェクトとして複製することもできます。

リストには「スライス」と呼ばれる機能が使えます。この「スライス」を使ったリスト要素の複製方法を学びましょう。

スライス機能の書き方

リストのスライスは、次のように3つの要素を指定します。

スライスの書式
 リスト変数名開始番号終了番号間隔

使用例
 list2 = list1[0:2:1]

備考
 開始番号の省略時は「0」を指定したものとする
 終了番号の省略時はリスト要素数を指定したものとする
 間隔の省略時は「1」を指定したものとする

このような初期設定値のことを、IT業界では「デフォルト値」と呼ぶ。

「開始番号」「終了番号」「間隔」の値を省略することも可能なため、様々な書き方が可能です。

スライス機能を使ってリストオブジェクトを複製

まずは、スライスを使ってリストオブジェクトを完全複製してみましょう。次のように書くことができます。

list1 = [0, 10, 20, 30, 40, 50]

# リストのスライス機能でリストオブジェクトを複製
list2 = list1[::]

# 変数の参照先オブジェクトのIDを表示
print("list1:", list1, id(list1))
print("list2:", list2, id(list2))

異なるリストオブジェクトIDが「list2」変数に紐づいているのがわかります。

list1: [0, 10, 20, 30, 40, 50] 2145048683016
list2: [0, 10, 20, 30, 40, 50] 2145048683080

全てのスライス要素を省略した[::]の場合は、リスト全体を表現することになります。

スライスを使ったプログラム例と複製結果

スライス機能はいろいろな指定パターンがあるため、指定した値によってどのように複製されるかを把握しておくとよいでしょう。

list1 = [0, 10, 20, 30, 40, 50]

# リストのスライス機能でリストオブジェクトを複製
list2 = list1[1:4:]
list3 = list1[:5:]
list4 = list1[3::]
list5 = list1[::2]
list6 = list1[1::2]
list7 = list1[-2::]
list8 = list1[::-1]

# スライス結果の表示
print("元リスト", list1)
print("[1:4:] -->", list2)
print("[:5:]  -->", list3)
print("[3::]  -->", list4)
print("[::2]  -->", list5)
print("[1::2] -->", list6)
print("[-2::] -->", list7)
print("[::-1] -->", list8)
元リスト [0, 10, 20, 30, 40, 50]
[1:4:] --> [10, 20, 30]
[:5:]  --> [0, 10, 20, 30, 40]
[3::]  --> [30, 40, 50]
[::2]  --> [0, 20, 40]
[1::2] --> [10, 30, 50]
[-2::] --> [40, 50]
[::-1] --> [50, 40, 30, 20, 10, 0]

まとめると、次のような法則で取得できています。

スライス指定取得結果説明
[1:4:][10, 20, 30]要素[1]から要素[3]まで取得
[:5:][0, 10, 20, 30, 40]要素[0]から要素[4]まで取得
[3::][30, 40, 50]要素[3]から要素終端まで取得
[::2][0, 20, 40]要素[0]から2個間隔で取得
[1::2][10, 30, 50]要素[1]から2個間隔で取得
[-2::][40, 50]終端の[-2]から要素終端まで取得
[::-1][50, 40, 30, 20, 10, 0]要素終端から始端にむけて取得

まず、注意したいのは「終了番号」の扱いであり、「終了番号-1」のインデックスまでが抽出されます。

そして、「間隔」の値に負値を設定すると「開始番号」のデフォルト値が要素終端へ、「終了値」のデフォルト値が始端要素へ変更されます。

ナナ
ナナ

スライスで指定した「終了番号」のインデックス要素は、抽出の中には含まれないことに注意しましょう!