Python

【Python 4】コンテナ(リストとタプル)

環境

macOS Big Sur
Python3.8

複数のデータを格納し管理するデータ構造を「コンテナ」といいます。コンテナにはリスト、タプル、集合、辞書があります。

リスト

「リスト」は複数のデータを順番に並べて管理したものです。角括弧([]:Square Bracket)で囲み、データ(「要素」といいます。)をカンマ(,)で区切って定義します。

要素は数値や文字列、リスト等なんでも扱えます。

角括弧だけの場合は、要素の無い空のリストを定義したことになります。空のリストは「list()関数」でも定義できます。

リストの定義

リスト名 = [値1, 値2, 値3, ...]

リスト名 = [] ※空のリスト

リスト名 = list() ※空のリスト

>>> a = [1, 2, 3, 4]

 

要素の呼び出し

リスト内の各要素には、「0」から始まるインデックスが左から順番に割り振られています。

インデックスを指定することで、要素を呼び出すことができます。リスト名に角括弧をつけ、その括弧内にインデックスを指定します。

また、インデックスにはマイナスを指定することもでき、この場合、最後から順番に「-1」、「-2」と割り振られています。

リストのインデックス指定

リスト名[インデックス]

>>> a = [1, 2, 3, 4]
>>> a[1]
2
>>> a[-1]
4

 

要素の変更

インデックスを指定し、別のデータを代入することで要素を変更することができます。このように自身を変更できるデータのことを「ミュータブル」(mutable、変更可能)といいます。逆に変更できないデータのことを「イミュータブル」(immutable、変更不可能)といいます。

>>> a = [1, 2, 3, 4]
>>> a[3] = 5
>>> a
[1, 2, 3, 5]

 

データ自身を変更する操作のことを「破壊的」あるいは「インプレース」な操作といいます。例えば、インデックスを指定したリストの要素変更は破壊的操作です。

なお、別のデータを再度代入することは破壊的操作ではありません。「再定義」になります。

要素の連結

+演算子でリストを連結、×演算子でリストを複数個連結することもできます。その際は新しいリストが作成されます。

>>> [1, 2] + [3, 4]
[1, 2, 3, 4]
>>> [1, 2] * 2
[1, 2, 1, 2]

 

要素の削除

「del文」で要素を削除することができます。

要素の削除

del リスト名[インデックス]

>>> a = [1, 2, 3, 4]
>>> del a[3]
>>> a
[1, 2, 3]
>>> del a  # 変数自体を削除することもできます。

 

多次元配列

リストのリストを作ることで2次元配列を作ることができます。

>>> a = [
... [1, 2, 3, 4],
... ['Ⅰ', 'Ⅱ', 'Ⅲ', 'Ⅳ']
... ]
>>> a[1]
['Ⅰ', 'Ⅱ', 'Ⅲ', 'Ⅳ']
>>> a[1][2]
'Ⅲ'
# 1つめのインデックス指定で['Ⅰ', 'Ⅱ', 'Ⅲ', 'Ⅳ']を取り出し、
# 2つめのインデックス指定で'Ⅲ'を取り出しています。

 

同様の考え方で多次元配列を作ることもできます。例えば3次元配列は以下のようになります。

>>> a = [
... [[1, 2, 3, 4],['Ⅰ', 'Ⅱ', 'Ⅲ', 'Ⅳ']],
... [['a', 'b', 'c', 'd'], ['A', 'B', 'C', 'D']]
... ]
>>> a[1]
[['a', 'b', 'c', 'd'], ['A', 'B', 'C', 'D']]
>>> a[1][1]
['A', 'B', 'C', 'D']
>>> a[1][1][1]
'B'

 

多次元配列の考え方はコンテナ共通です。

スライス

「スライス」を利用して複数の要素を取り出すことができます。

スライス

リスト名[最初のインデックス:最後のインデックス+1]

最初のインデックスを指定しない場合は「リストの最初」から、最後のインデックスを指定しない場合は「リストの最後」まで指定されます。

なお、スライスはインデックスで管理している型に使用することができます。リストの他、文字列、タプルでも使用することができます。

>>> a = [1, 2, 3, 4]
>>> a[1:3]
[2, 3]
>>> a[-3:]  # マイナスのインデックスも指定できます。-3から最後までの要素。
[2, 3, 4]
>>> a[2:4] = [5, 6]  # リストであれば要素の変更もできます。
>>> a
[1, 2, 5, 6]
>>> del a[1:3]  # スライスを使って要素の削除もできます。
>>> a
[1, 6]

 

指定したインデックスの前に切り込みを入れて取り出すイメージです。

a[1:3] [1,| 2, 3,| 4] → [2, 3]

インデックスの他に、増分(ステップ)を指定することもできます。ステップの分飛ばして要素を取り出すことができます。

スライス

リスト名[最初のインデックス:最後のインデックス+1:ステップ]

>>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> a[2:9]
[3, 4, 5, 6, 7, 8, 9]
>>> a[2:9:2]  # インデックス2から8まで、2ずつインデックスを増加させて要素を取り出します。
[3, 5, 7, 9]
>>> a[::-1]  # ステップに「-1」のみ指定すると逆順にソートできます。
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

 

スライスの活用

変数を定義すると、内部的にはデータの保存場所が変数に格納されます。そして、その変数(変数1とします。)を他の変数(変数2とします。)に代入すると、変数2は変数1と同じ保存場所を参照するようになります。これを「参照渡し」(byref)といいます。(値そのものを渡すことを「値渡し」(byvalue)といいます。Pythonでは、常に参照渡しとなります。)

参照渡しをした場合、変数はお互いに同じデータを参照していることになるので、一方の変数に変更を加えるともう一方の変数も影響を受けます。

>>> a = [1, 2, 3, 4]
>>> b = a  # bにaを代入します。参照渡しです。
>>> a[3] = 5  # aに行った変更はbにも適用されます。
>>> a
[1, 2, 3, 5]
>>> b
[1, 2, 3, 5]

 

リストで、異なる参照先を渡したい場合は、スライスを活用することで解決できます。スライスは独立した新しいリストを作成するため、変数に代入すると、新たな値の保存場所を参照するよう定義できます。

ただし、リストの要素に別のリストが存在する場合は、要素のリストだけは参照先が変わらないので注意が必要です。

>>> a = [1, 2, 3, 4]
>>> b = a[:]  # スライスで新しいリストbを作成。参照が変わります。
>>> a[3] = 5
>>> a
[1, 2, 3, 5]
>>> b
[1, 2, 3, 4]

>>> a = [1, 2, [3, 4]]
>>> b = a[:]  # 要素のリスト[3, 4]部分だけ参照先は変わりません。
>>> b
[1, 2, [3, 4]]
>>> a[2][1] = 5
>>> a
[1, 2, [3, 5]]
>>> b
[1, 2, [3, 5]]

 

Pythonでは、代入はデータの場所、つまり参照先を変数に渡す行為です。データ自身が変更されれば同じデータを参照している変数は全て影響を受けます。

別のデータが代入(再定義)されれば変数の参照先が変更されるため、独立した変数となります。

同じ内容のデータを持つ変数でも、「参照渡し」と「変数の再定義」では、その振る舞いは全く異なります。

# 参照渡しの場合。
>>> a = [1, 2]  # aは[1, 2]の場所を参照します。
>>> b = a  # bはaの参照先である[1, 2]の場所を参照します。参照渡しです。
>>> a[1] = 3  # 参照先の[1, 2]を[1, 3]に変更します。
>>> a  # aもbも変更後のリストを表示します。
[1, 3]
>>> b
[1, 3]

# 変数を再定義した場合。
>>> a = [1, 2]  # aは[1, 2]の場所を参照します。
>>> b = a  # bはaの参照先である[1, 2]の場所を参照します。これは参照渡しです。
>>> a = [1, 2]  # aは参照先を[1, 2]から別の[1, 2]に変更(再定義)します。
>>> a[1] = 3  # aに変更を加えても、bに影響はありません。
>>> a
[1, 3]
>>> b  # aとbは別の場所を参照しているためです。
[1, 2]

 

リストのスライスを代入すると、データの参照先が変わるため、独立した変数が作られますが、要素にリストが含まれている場合、その要素(便宜上、「内側のリスト」と表現します。対して変数に代入されたリスト全体を「外側のリスト」と表現します。)の参照先は変更されていません。そのため、内側のリストに変更を加えると他のリストにも影響が及びます。

スライスの様に外側のリストのみ新しく複製することを「浅いコピー(shallow copy)」といい、内側のリストを含む全てを新しく複製することを「深いコピー(deep copy)」といいます。

「浅いコピー」については、「ミュータブルなデータ」を扱う場合に意識する必要があります。

「深いコピー」については、「ミュータブルなデータを要素に持っているデータ」を扱う場合に意識する必要があります。

便利な関数

>>> a = [1, 2, 3, 4]
>>> sum(a)  # 「sum()関数」でリストの合計が計算できます。
10
>>> max(a)  # 「max()関数」でリストの最大値を求めることができます。
4
>>> min(a)  # 「min()関数」でリストの最小値を求めることができます。
1
>>> len(a)  # 「len()関数」でリストの要素数を求めることができます。
4
# sum(),max(),min(),len()はリストの他、タプル・集合・辞書でも使用できます。
# 辞書の場合はキーに適用されます。

 

タプル

「タプル」はリストに似て複数の要素を持ち、インデックスによる呼び出しもできますが、要素の変更ができないという特徴を持ちます(イミュータブル)。

タプルは丸括弧(():Bracket)で囲み、データ(要素)をカンマ(,)で区切って定義します。

丸括弧で囲まず、要素をカンマで区切るだけでも一つのタプルが定義できます。これを「タプル・パッキング」といいます。

丸括弧だけの場合は、要素の無い空のタプルを定義したことになります。空のタプルは「tuple()関数」でも定義できます。

タプルの定義

タプル名 = (値1, 値2, 値3, ...)

※要素が1つの場合は(値1,)というように要素の後にカンマ(,)を付けます。

タプル名 = () ※空のタプル

タプル名 = tuple() ※空のタプル

>>> a = (1, 2, 3)
>>> b = 4, 5, 6  # タプル・パッキング。(4, 5, 6)と同じです。
>>> c = (7,)  # 要素が一つの場合は、要素の後にカンマをつけます。
>>> d = 8,  # 要素が一つの場合も()無しで定義できます。

 

タプルはイミュータブルですが、リストのようなミュータブルなデータを要素に持つことができます。

ミュータブルな要素が含まれている場合、参照先のデータが変更されるとタプルも影響を受けてしまうので注意が必要です。

>>> a = [3, 4]
>>> b = (1, 2, a)  # タプルはリストも要素にできます。
>>> b
(1, 2, [3, 4])
>>> a[1] = 5  # 参照先のリストが変更されると、タプルも影響を受けます。
>>> a
[3, 5]
>>> b
(1, 2, [3, 5])

 

要素の呼び出し

タプル内の各要素には、リストと同様にインデックスが割り振られています。

インデックスを指定することで、要素を呼び出すことができます。タプル名に角括弧をつけ、その括弧内にインデックスを指定します。ただし、変更や削除はできません。

タプルのインデックス指定

タプル名[インデックス]

>>> a = (1, 2, 3, 4)
>>> a[1]
2
>>> a[3] = 5  # 要素の変更はできません。
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> del a[1]  # 要素の削除はできません。
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object doesn't support item deletion

 

要素の連結

リストと同様に、+演算子でタプルを連結、×演算子でタプルを複数個連結することもできます。その際は新しいタプルが作成されます。

>>> (1, 2) + (3, 4)
(1, 2, 3, 4)
>>> (1, 2) * 2
(1, 2, 1, 2)

 

シーケンス・アンパッキング

リストまたはタプルを使うことで、複数の変数に同時に代入することができます。これを「シーケンス・アンパッキング」といいます。

左辺に変数を、右辺にリストまたはタプルを指定し、代入演算子で繋ぎます。なお、変数の数と、リストまたはタプルの要素数は一致している必要があります。

>>> a, b = [1, 2]  # aに1、bに2が代入されます。
>>> a, b = (1, 2)  # 上と同様aに1、bに2が代入されます。
>>> a, b = 1, 2  # 「多重代入」といいます。
# 多重代入は、タプル・パッキングとシーケンス・アンパッキングの組み合わせです。