Python勉強記(9) リストと沼
リスト(list)とは?
Pythonでは、リストが動的配列として実装されています。
複雑な点として、リストはPythonの組み込みデータ型でありつつ、標準ライブラリのarrayを使っても定義可能です。どちらも型がlistであると確認することができます。
a = [1, 2, 3, 4, 5] # 組み込みList型
print(a)
>> [1, 2, 3, 4, 5]
print(type(a))
>> <class 'list'>
import array as ary # 標準ライブラリ array のList
ary.a = [1, 2, 3, 4, 5]
print(ary.a)
>> [1, 2, 3, 4, 5]
print(type(ary.a)) # こちらもlist型。
>> <class 'list'>
print(a==ary.a) # a==ary.a ?
>> True
簡単な使い方
Pythonの標準的な教科書ではリストとタプルがこの順で説明されることが多いですが、タプルから学び始めるのも一つの方法です。
リストを深く掘り下げる前に、基本的な使い方をマスターすることが重要です。まず、
・リストはタプルの変更可能なバージョンである
と理解することから始めます。
リストの最も簡単な使用法は一次元配列です。リストを使用して、少し飽きてきたけれども、フィボナッチ数列の改良版を作成してみます。
def fib2(n):
fib_list = [] # リストを初期化する
a, b = 0, 1
while (a < n):
fib_list.append(a) # 数列をリストに追加する。
a, b = b, a + b
return fib_list
k = int(input())
print(fib2(k))
前回作成したfib()関数では、関数内でprint(a)を使用していた点がいまいちでした。
上記のfib2()関数は、整数nを引数として受け取り、n以下のフィボナッチ数列をリストとして返します。漸化式による計算は同様ですが、fib_listという新しいリストを作成し、fib_list.append(a)を使用して項を追加しながら計算を行います。
プログラム内で計算する範囲を、fib(1000)のように書いていた部分をinput()を使ってキーボードから入力できるように変更しました。最後にprint(fib2(k))を使用して、リストに格納された数列を一度に出力します。
Input: 20
>> [0, 1, 1, 2, 3, 5, 8]
操作方法
リストは変更可能(ミュータブル)なシーケンス型で、タプルよりも柔軟性が高いです。要素の追加、削除、ソートなどが自由に行えます。
例えば、list.append(x)はリストの末尾に要素xを追加し、list.pop()はリストの末尾から要素を1つ削除し(削除した要素を返します)、これらはメソッドと呼ばれます。
a = ['a','b','c','d','e']
a.append('f') # 'f' を追加する
print(a)
>> ['a', 'b', 'c', 'd', 'e', 'f']
a.pop() # 最終文字を取り除く
print(a)
>> ['a', 'b', 'c', 'd', 'e']
a[1]='x' # a[1} を'x' に書き換える。
print(a)
>> ['a', 'x', 'c', 'd', 'e']
del a[1] # a[1] を消去する。
print(a)
>> ['a', 'c', 'd', 'e']
print a[1} # a[1]の値を確認する。
>> ['c']
リストのソート(メソッドと関数の違いも)
リストのソートには、二つの方法が存在します。
リストそのものをソートしたい。
元リストは壊さずに、ソートした新しいリストを得たい。
まず、list.sort() メソッドを使用してリストをソートします。逆順にソートする場合は、引数に reverse=True を指定します。
a = ['a','b','c','d','e']
a.sort(reverse=True) # 逆順にソート
print(a)
>> ['e', 'd', 'c', 'b', 'a']
a.sort() #昇順にソート(もとに戻す)
print(a)
>> ['a', 'b', 'c', 'd', 'e']
2番目の方法では、組み込み関数のsorted()を使用します。リストをsorted()に渡して、その結果を変数bで受け取ります。
※この方法を使えば、タプルのソート結果も得られます。
a = ['a','b','c','d','e']
b = sorted(a,reverse=True) # a のソート結果を返す(aは壊さない)
print(b)
>> ['e', 'd', 'c', 'b', 'a']
print(a)
>> ['a', 'b', 'c', 'd', 'e'] # aは壊していないのでそのまま。
a = ('a','b','c','d','e') # タプルをソートしたいとき
b = sorted(a,reverse=True) # a がタプルでも問題ない。
print(b)
>> ['e', 'd', 'c', 'b', 'a'] # 結果はリスト。タプルが欲しければ変換する。
b=tuple(b)
print(b)
>> ('e', 'd', 'c', 'b', 'a')
どちらを使うかは状況によって異なりますが、ここで学んだ重要な点はメソッドと関数の違いです。
リストの沼とは
データはすべてオブジェクト
Pythonプログラムでは、すべてのデータがオブジェクトとして扱われます。
公式ドキュメントではオブジェクトの定義が記載されていますが、オブジェクト指向言語に精通している人には明白であっても、初学者には理解しにくいことがあります。重要なところを引用します。
今回の話題は同一性についてです。
同一性は、id関数やis演算子を使って確認できます。
以下の条件を満たす場合、オブジェクトxとyは同一とされます。。
id(x) == id(y)
x is y ==True
オブジェクトの代入がどのような結果になるか確認します。
print(id(1))
>> 140708457609656 # オブジェクト1 のid
x = 1
y = x
print(id(1), id(x), id(y))
>> 140708457609656 140708457609656 140708457609656 # 1,x,y は同一なオブジェクト
print(x is y is 1)
>> True # Warningが出るかもしれません。
上記の代入によって、同一のオブジェクト1が変数xとyによって参照されています。つまり、変数xとyはオブジェクト1を指しています。公式ドキュメントによれば、id(x)はxのメモリアドレスを返すと記されています。
上の続きで、x += 1 とするとどうなるでしょう?
x += 1
print(id(1), id(x), id(y))
>> 140708457609656 140708457609688 140708457609656
print(id(2))
>> 140708457609688
id(x) の値のみ変化し、id(2)と等しくなりました!
つまりx は2を参照するようになりました。yは触ってないので1を参照したままです。
次にリストの代入を試してみます。
x = [1, 2, 3]
y = x
print(x, y)
>> [1, 2, 3] [1, 2, 3]
x[0] = 99
print(x, y)
>> [99, 2, 3] [99, 2, 3] # y の方も変化した?
xの要素だけ触ったのに、y の方も変わったのは不思議だと思いませんか?
そのまえの代入y = x において、xとy は同じオブジェクトを参照しているため、x の要素を変えたら連動してyの値も変わる。よく考えたら当たり前ですが、ブログラムの現場ではハマりそうな沼だと思いました。
ハマらないように注意しないと・・・。