見出し画像

Python勉強記(9) リストと沼

リスト(list)とは?

抽象データ型としてのリスト (: list) は、順序つきのデータコンテナとして定義される。
リストはたいてい配列連結リストを使って実装される。これは配列や連結リストと似た特性を持っているからである。また連結リストのことを単にリストと呼ぶこともある。順序を持つ点を強調してシーケンス (; : sequence) と呼び、連結リストと区別することもある。

出典:Wikipedia

Pythonにおいて、リストは動的配列として実装されています。

複雑なことに、リストは組み込みのデータ型でありながら、標準ライブラリのarrayでも定義できます。両方ともtype が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の一般的な教科書では、リスト、タプルの順に記述されますが、私はタプルを先に調べました。
リストの沼に踏み込む前に、簡単な使い方を抑えておきます。最初は

  • リストはタプルの書き込み可能バージョンである。

と理解していてOK。

リストの一番易しい使い方は一次元配列です。
リストを利用して(もうかなり飽きてきたが)フィボナッチ数列の改良版をつくってみます。

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個要素を削除(削除した値をreturnする)メソッドです。
(メソッドについては下記)

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']

リストのソート(メソッドと関数の違いも)

リストをソートするには、2種類方法があります。

  1. リストそのものをソートしたい。

  2. 元リストは壊さずに、ソートした新しいリストを得たい。

1番目を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')

どちらを使うかは場合によりけりですが、ここで重要なことを学びました。メソッドと関数の違いです。

1. メソッドはオブジェクトそのものを操作する。
2. 関数は引数を渡して返り値を受け取る。

リストの沼とは

データはすべてオブジェクト

Pythonプログラムでのデータは全てオブジェクトです。

公式ドキュメントに、オブジェクトとは何ぞや?が書かれていますが、オブジェクト指向言語に慣れている人には自明ですが、我々初学者にはすっと頭に入ってきません。

重要なところを引用します。

すべてのオブジェクトは、同一性 (identity)、型、値をもっています。

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の値も変わる。よく考えたら当たり前ですが、ブログラムの現場ではハマりそうな沼だと思いました。

ハマらないように注意しないと・・・。

この記事が参加している募集

この記事が気に入ったらサポートをしてみませんか?