リスト型の基本
初期化・定義
l = ["pen", "gum", "scale"]
print(l)
結果:
['pen', 'gum', 'scale']
リスト型とは?
Excel の 1行(横方向に1行文)、あるいは1列(縦方向に1列文)のデータを格納するデータ形式です。 Excel の行・あるいは列のように同種のデータをひとまとまりにするために使用します。一連のデータを処理する機能(メソッド)を備えています。
例えば、
address = [“100-0000”, “Tokyo-to”, “Python”, “male”, “Hobby”]
といった、一連のデータではないまとまりのデータを格納することもできます。しかし、こういった形式ではもっと適したデータ構造があり、後から参照する際に分かりづらくなるため、できるだけ避けた方が無難でしょう。
リスト型の処理に便利な for … in … 構造
for in の基本
l = ['pen', 'gum', 'scale']
for i in l:
print(i)
結果:
pen
gum
scale
for in の解説では、 range 関数を用いて数字のカウントアップでループの基本を学びました。実は、 range 関数の返り値も、リスト型も Iterable (イテラブル)なデータ型です。イテラブルとは、「繰り返し可能な」と訳されるとおり、 for などのループで「繰り返し処理が可能」です。
このサンプルでは、リスト型の変数 l から、ひとつずつその中身を取り出して、変数 i に代入しています。そして、代入された変数 i を print 関数により出力されています。
これが、 Python におけるリスト型、イテラブルな型の基本です。データ処理、機械学習などで用いられる numpy, pandas などでも使われる基本の基本です。
リスト型の個別の値へのアクセス
インデックスによる通常のアクセス
l = ['pen', 'gum', 'scale', 'triangle', 'marker']
print(l[0]) #0 + 1 = 1番最初
print(l[2]) #1+ 2 = 3番目
文字列型のように、0 から 始まるインデックス・添え字によって個別の要素を取り出せる。
添え字の仕様は文字列と同様のため、
l = ['pen', 'gum', 'scale', 'triangle', 'marker']
print(l[1:3]) #2番目から4番目の手前まで(3番目まで)
print(l[2:]) #3番目から全て
print(l[:2]) #3番目の手前まで(2番目まで)
print(l[:]) #すべて
結果:
['gum', 'scale']
['scale', 'triangle', 'marker']
['pen', 'gum']
['pen', 'gum', 'scale', 'triangle', 'marker']
: の後ろ側の添え字は、エディタのキャレットをイメージすると分かりやすい。キャレット(カーソル)に挟まれている部分が選択範囲となる。
インデックスによる逆順のアクセス
リスト末尾のインデックスを [-1] として、1ずつ減っていく。範囲指定時のカーソルの法則も同様。
l = ['pen', 'gum', 'scale', 'triangle', 'marker']
print(l[-1])
print(l[-3:-1])
print(l[-2:])
print(l[2:-1])
結果:
marker
['scale', 'triangle']
['triangle', 'marker']
['scale', 'triangle']
リスト型の変わった値の取り出し型
l = ['pen', 'gum', 'scale', 'triangle', 'marker']
print(l[::-1]) #逆順
print(l[::2]) #ひとつ飛ばし
結果:
['marker', 'triangle', 'scale', 'gum', 'pen']
['pen', 'scale', 'marker']
添え字の範囲指定の後に、更に : を付け加えることで値の取り出し方を変更する。
-1 で後ろから。1以外の数値で、その個数ごとに取得(2であれば1つ飛ばし)する。余り使わないが、日ごとのデータを30日ごと、7日ごとなどの一定期間ごとにサンプル抽出するような場合に使われることもある。
リスト型の切り出し(スライス)から新しいリスト型の作成
l = ['pen', 'gum', 'scale', 'triangle', 'marker']
m = l[1:4]
print(m)
結果:
['gum', 'scale', 'triangle']
新しい変数に代入すれば、一部分を切り出した配列とすることができる。
リスト型の値の変更とコピー
l = ['pen', 'gum', 'scale', 'triangle', 'marker']
m = l
print(m)
結果:
['pen', 'gum', 'scale', 'triangle', 'marker']
このような単純な代入は機能しているように見えるが、実は機能していない。
l = ['pen', 'gum', 'scale', 'triangle', 'marker']
m = l
print(m)
l[0] = 'pencil'
print(l)
結果:
['pen', 'gum', 'scale', 'triangle', 'marker']
['pencil', 'gum', 'scale', 'triangle', 'marker']
インデックスを指定して値を代入すると、リストの中の値が書き換わる。ここで更に、追加で m を出力すると、
l = ['pen', 'gum', 'scale', 'triangle', 'marker']
m = l
print(m)
l[0] = 'pencil'
print(l)
print(m)
結果:
['pen', 'gum', 'scale', 'triangle', 'marker']
['pencil', 'gum', 'scale', 'triangle', 'marker']
['pencil', 'gum', 'scale', 'triangle', 'marker']
何故か、 m の中身まで変わっていることが分かります。これは、リスト型の代入が値のコピーではなく、参照( reference )のコピーだからです。
プログラミング・コンピューターに詳しくないと難しい概念の参照ですが、例を挙げると「コピペしたツイート」が値のコピー、「RT したツイートや埋め込みされたツイート」が参照となります。後者のRT や埋め込みされたツイートでは、元のツイートが削除や編集された場合(編集は、 Twitter ではありませんが)、RT 先や埋め込み先でも内容が変化・参照できなくなったりします。
これが参照の代入となります。
参照の代入とよく似た概念に参照渡しがあります。動作としては全く一緒ですが、参照渡しとは、関数やメソッドに使われる表現です。関数やメソッドで、参照渡しされた変数(例えばリスト型)が変更された場合、関数の外でも変更が適用されます。
参照と参照渡しがごっちゃになって、全て参照渡しと言うこともありますが、大体通じるので実用上は問題ないでしょう。
では、リスト型のように参照の代入や参照渡しが標準となる変数をコピーしたい場合はどうするかですが、明示的にコピーを指定します。
l = ['pen', 'gum', 'scale', 'triangle', 'marker']
m = l.copy() #コピーメソッドを使う
n = l[:] #インデックスの書式を使う
l[0] = 'pencil'
print(l)
print(m)
print(n)
結果:
['pencil', 'gum', 'scale', 'triangle', 'marker']
['pen', 'gum', 'scale', 'triangle', 'marker']
['pen', 'gum', 'scale', 'triangle', 'marker']
変数 n , m ともに変化していないことが分かります。
m へは、リスト型のメソッド、 copy を使用しています。その名の通り、リストのコピーを作成して返します。
n へは、 Iterable 型でなければ使えませんが、インデックスの記法でリスト全体を返すように記載しています。こうすると、リスト全体と同じ内容のリストをスライス(切り出)したことになるので結果的にコピーが作成されます。割とデータ分析のソースコードで見ますが、他の言語では使えないことが多い記法なので、他言語経験者が混乱しがちです。 copy メソッドの方が、意図が伝わりやすく、読みやすいコードと言えるでしょう。
2次元のリスト構造(多次元のリスト)
最初に、リスト構造は Excel の行、または列のようなものと説明しました。では、 Excel 同様に行も列も持ったデータ構造はあるのでしょうか?
この場合は、リストを入れ子(ネスト)にして 2 次元のリストとします。
table = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
print(table)
結果:
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
3×3 の、9つの内容を持つ 2次元のデータ構造となりました。 [] 角括弧の中にさらに角括弧がある点、 , カンマの位置に気をつけてください。
ただ、これだけではいまいち、リストに 9つのデータを入れているだけに見えてしまいます。そこで、 for … in … 構造でデータがどのようになっているか確認してみます。
table = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
for row in table:
print(row)
print('---------')
結果:
[1, 2, 3]
---------
[4, 5, 6]
---------
[7, 8, 9]
---------
for in が3回だけ繰り返されて、要素の数が 3つである配列が3回出力されました。各行に 3つのセルがある表のイメージが湧いてきたでしょうか。
多重ループによる多次元リストの処理
さらに、細かく多次元リストの中身を取り出します。
/table = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
for row in table:
for cell in row:
print(cell)
print('---------')
結果:
1
2
3
---------
4
5
6
---------
7
8
9
---------
print 関数の出力が int 型となり、数値がバラバラに取り出せていることが分かります。また、区切り線により、数値 3つごとに内側の for を抜けて、最初の for に戻っています。
内側の for in の、 in の後ろにあるリストは、外側の for で、 table から値が取り出されている row 変数となっています。多次元リストではこのように、リストの次元数と同じだけの for in 構造を用意し、外側で取り出されたリストを内側の for in 構造の入力とすることで、中身を取り出すことができます。
Python では map 関数などの iterable を一括で処理する関数を用いることが多いですが、もっともシンプルに(原始的に)多次元構造を処理する方法なので覚えておきましょう。
多次元リストの値を直接取り出す
for in 構造ではなく、インデックスを用いて直接取り出す場合には、
table = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
print(table[1][2]) #外側で2番目(定義の2行目)、内側で3番目の要素
結果:
6
このように、[]角括弧を複数つなげて書くことで外側から内側へ、要素のインデックスをしていきます。3次元のリストであれば、[1][2][3]とすることで、一番内側で定義されたリストの4番目の要素にアクセスすることになります。
なお、インデックスを途中までしか指定しなかった場合は、
table = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
print(table[1]) #外側で2番目の要素
結果:
[4, 5, 6]
内側のリストが、リストとして出力されます。 for in が 1重だったときと同様の動作になることを確認してください。