はじめに
pythonでよく使う組み込みのコレクション型には、list, tuple, dict, setなどがあります。ただ、なんとなく使っている、いつもlist、それぞれ微妙に使い勝手が違って困る、という人も多いと思います。そこで、これらについて解説します。
目次
コレクション型ってなに?
とりあえず、「コレクション型」という用語は公式に使われているものではなく、便宜的にそう読んでいるだけなので注意してください。「中にオブジェクトを格納できる型(クラス)」程度の意味です。
ドキュメントによれば、pythonの主要な組み込み型には以下のような区別があります。
主要な組み込み型は、数値、シーケンス、マッピング、クラス、インスタンス、および例外です。
(なお、上のページはつらつらと読むとそれだけで勉強になります。この記事を読んで物足りないと思った人におすすめです。)
さて、タイトルに含めた4つのうち、listとtupleはシーケンス、dictはマッピングです。setはどれにも当てはまりません。
シーケンス、マッピングという言葉の意味は、なんとなくわかる人も多いと思います。シーケンスは順番があって連続的に、あるいはインデックスを指定したりしてアクセス可能なもの、マッピングはシーケンスと異なって順番を持ちませんが、dictのように任意のキーと値の対応付けを記録可能なものです(というか、基本的にdictとその亜種しかないと思います)。
こういう「コレクション型」はpythonでプログラミングをする上ではとても重要なので、とりあえず組み込みの主だったものはすべて覚えておきましょう。どんなコードでも何らかの形で利用していたりするので、知らないと困ります。
listについて
基本
コレクション型の中でもっとも代表的なのはlist(リスト)ですね。色々な場面で汎用的に使われます。初心者の方はとりあえずlistを覚えて、慣れないうちはなんでもlistで書くと良いと思います。
リテラル*1で書く場合はこんな感じです。
>>> print(lst1) # 空のリスト [] >>> lst2 = [1, 2, 3] >>> print(lst2) [1, 2, 3]
要素を末尾に追加したければ、appendが使えます。
>>> lst1.append(4) >>> lst2.append(5) >>> print(lst1) [4] >>> print(lst2) [1, 2, 3, 5]
逆にlist以外でappendが使えるものはあまり(というか恐らくまったく)ありません。ついでに書いておくと、numpyやpandasにもappendがありますが、あれはあれでまた違った挙動になります。
「拡張」というか、listを含む他のコレクション型など*2の中身をまとめて突っ込みたいときは、extendというメソッドが使えます。
>>> lst1.extend(lst2) >>> print(lst1) [4, 1, 2, 3, 5]
あとは普通にインデックスで要素を取得したり、for文に渡してループしたりもできます。
>>> print(lst1[0]) 4 >>> for x in lst1: ... print(x) ... 4 1 2 3 5
インデックスで要素を削除したい場合はdel文で行えます。また、pop(こちらもインデックスで指定し、除去した要素を返す), remove(こちらはオブジェクトの値が一致するものを除去)などで取り除くこともできます。
>>> del lst1[3] >>> print(lst1) [4, 1, 2, 5]
ただ、あまり使っているのを見ませんね。これをやるとインデックスが狂う上、ループと組み合わせると面倒くさい問題もあります。
【python】listをforループで回してremoveしたら思い通りにならない - 静かなる名辞
append以外でlistの長さが減るような処理は積極的にやらない、という傾向が強いのではないでしょうか。
listは「ミュータブルなシーケンス」という分類になり、実はとてもたくさんの操作が行えます。すべてはカバーできないので、ここ↓を見てください。
組み込み型 — Python 3.7.4 ドキュメント | シーケンス型 --- list, tuple, range
繰り返しになりますが、これを読むととても勉強になります。
内包表記
listといえばリスト内包表記というくらいよく使われるものです。
>>> print([x + 100 for x in range(5)]) [100, 101, 102, 103, 104]
難しいものではないので、慣れましょう。
tupleについて
基本
tuple(タプル)はlistの中身を一切変更できなくしたような使い勝手です。
>>> tup1 = () # 空のtuple >>> tup2 = (1, 2, 3) >>> print(tup1) () >>> print(tup2) (1, 2, 3)
注意しないといけないのは、外側の丸括弧()はtupleの本質ではないという点です。カンマがあればtupleは作れます。
>>> tup2 = 1, 2, 3 >>> tup2 (1, 2, 3)
え、カンマなんかあちこちで使うじゃん(関数の引数リストとか)と思うと思いますが、原則的には「他の構文とみなせないときはtupleとみなす」というルールです。なんかややこしいですね。
では丸括弧は何か? というと、数式で(a + b) * 2とかやるときの丸括弧と同じで、評価の順番を決めているだけです。これがあることで、関数の引数リストなどの他の構文と混同されないようにtupleを表すことができます。
このことを知らないと、要素数1つのtupleを作りたいとき、こういうものを書いてしまいます。
>>> this_is_not_tuple = (4) >>> print(type(this_is_not_tuple), this_is_not_tuple) <class 'int'> 4
要素数1のtupleは、下のようにしてください。
>>> this_is_tuple = (4, ) >>> print(type(this_is_tuple), this_is_tuple) <class 'tuple'> (4,)
応用
中身の変更できないtupleなんか何に使えるんだと思われるかもしれませんが、tupleのようなイミュータブル(変更不可能)なオブジェクトはhashableという大切な性質を持っています。後述のdictやsetのキー、要素にしたいとき、データをtupleで表現するということがよく行われます。
tupleには内包表記はありません。内包表記的なことをしたい場合は、ジェネレータ式と組み合わせて以下のように書くと手っ取り早いでしょう。
>>> tuple(x - 100 for x in range(5)) (-100, -99, -98, -97, -96)
dict
基本
dict(ディクトと呼びますが、辞書という訳の方が一般的です)はキーと値の対応付けを保持します。
リテラルではこう書きます。
>>> dct1 = {} # 空のdict >>> dct2 = {"hoge":0, "fuga":1} >>> print(type(dct1), dct1) <class 'dict'> {} >>> print(dct2) {'hoge': 0, 'fuga': 1}
{キー:値, ...}という感じの構文です。簡単ですね。
値の追加には普通は代入を使います。また、累積代入文なども使えます。
>>> dct2["piyo"] = 2 >>> print(dct2) {'hoge': 0, 'fuga': 1, 'piyo': 2} >>> dct2["piyo"] += 999 >>> print(dct2) {'hoge': 0, 'fuga': 1, 'piyo': 1001}
削除はdel文でいいのですが、あまり使わないでしょう。
直接for文などに渡すと、キーだけが出てきます。
>>> for x in dct2: ... print(x) ... hoge fuga piyo
keys, values, itemsというメソッドでそれぞれキー、値、キーと値のペアを取り出せます。必要に応じて活用してください。
>>> for k in dct2.keys(): ... print(k) ... hoge fuga piyo >>> for v in dct2.values(): ... print(v) ... 0 1 1001 >>> for k, v in dct2.items(): ... print(k, v) ... hoge 0 fuga 1 piyo 1001
重要な性質
dictにはいくつか覚えておかないといけない性質があります。
まず、tupleのところでも述べたとおり、キーにはimmutableなオブジェクトしか使えません。
>>> dct3 = {[1, 2]:3} Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'list' >>> dct3 = {(1, 2):3} >>> print(dct3) {(1, 2): 3}
また、基本的には順番の概念はない、ということも知っておく必要があります。伝統的なPythonでは、辞書のキー・値の順番は適当にシャッフルされていました(内部的には意味があったのですが、割愛)。
# Python 3.5で実行 >>> d = {"hoge":123, "fuga":456, "piyo":789} >>> print(d) {'fuga': 456, 'hoge': 123, 'piyo': 789}
最近のPython*3では順番を保持しますが、それでもappendのような順序を前提としたオペレーションは弱いので、扱いは以前とさほど変わりません。
# Python 3.6で実行 >>> d = {"hoge":123, "fuga":456, "piyo":789} >>> print(d) {'hoge': 123, 'fuga': 456, 'piyo': 789}
まだあります。上で書いたkeys, values, itemsはとても便利ですが、これらは辞書ビューオブジェクトという集合っぽいオブジェクトで、しかもリアルタイムで辞書の中身を反映してくれます。ただし、ループ中にいじったりすると意図しない結果になったり、エラーが出たりします。
ループで辞書の要素を削除しようと思ったらRuntimeError: dictionary changed size during iteration - 静かなる名辞
便利ですが、扱いには少し気をつけた方が良いのが辞書です。
内包表記
dictにも内包表記があります。書き方はリスト内包表記と辞書のリテラルを混ぜた感じです。
>>> dct4 = {i:i**2 for i in range(5)} >>> print(dct4) {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
まあ、これといって難しいことはないかと。
眷属たち
dictには覚えておくと便利な標準の亜種がいくつかあります。collectionsモジュールから使えます。
筆頭はdefaultdictでしょう。これはキーの初期値を適当に設定できるもので、データの集計などで威力を発揮します。
# 長さの異なる文字列ごとにグルーピングする処理 >>> lst = ["a", "b", "ab", "abc", "bcd"] >>> d = {} # defaultdictを使わない場合 >>> for x in lst: ... l = len(x) ... if l in d: ... d[l].append(x) ... else: ... d[l] = [x] ... >>> print(d) {1: ['a', 'b'], 2: ['ab'], 3: ['abc', 'bcd']} # defaultdictを使う場合 >>> from collections import defaultdict >>> d = defaultdict(list) >>> for x in lst: ... l = len(x) ... d[l].append(x) # d[l]がなければ勝手に空のリストを初期値にしてくれる ... >>> print(d) defaultdict(<class 'list'>, {1: ['a', 'b'], 2: ['ab'], 3: ['abc', 'bcd']})
他の使い方はdictとほぼ同じです。
また、Counterは引数の頻度を勝手に集計してくれます。これも辞書の眷属みたいなものです。
>>> from collections import Counter >>> lst = [1, 1, 2, 3, 4, 3, 2, 5] >>> cnt = Counter(lst) >>> print(cnt) Counter({1: 2, 2: 2, 3: 2, 4: 1, 5: 1}) >>> print(cnt[0]) 0 >>> print(cnt[1]) 2
このあたりを知っているかどうかでコードのクオリティが変わるので、覚えておきましょう。
collections --- コンテナデータ型 — Python 3.7.4 ドキュメント
set
基本
トリを飾るのはsetです(セットとも呼びますが、表記するときは集合型とするかsetとそのまま書くかのどちらかです)。
setは数学的な集合を表現できます。あるいは、dictをkeyだけにしたようなものと考えてもいいでしょう(内部ではどちらもハッシュテーブルを使っています)。
リテラルで書くとき注意しないといけないのは、空のsetをリテラルで表現する手段が基本的にないということです。コンストラクタを使ってset()で作ります(これまで触れていませんでしたが、list(), tuple(), dict()でそれぞれ空のコレクションを作れます)。
>>> s1 = set() >>> print(s1) set() >>> s2 = {1, 2, 3} >>> print(s2) {1, 2, 3}
集合なので、要素は重複しません。強制的に1つにまとめられます。これを利用して、list→setに変換してユニークな要素の数を数えるというテクニックもよく使われます。
>>> s3 = {1, 2, 3, 3} >>> print(s3) {1, 2, 3} >>> lst = [1, 2, 3, 3] >>> print(len(set(lst))) 3
集合なので、様々な演算が行なえます。
>>> s1 = {1, 2, 3, 4} >>> s2 = {3, 4, 5, 6} >>> print(s1 & s2) # AND {3, 4} >>> print(s1 | s2) # OR {1, 2, 3, 4, 5, 6} >>> print(s1 - s2) # 引き算 {1, 2} >>> print(s2 - s1) {5, 6} >>> print(s1 ^ s2) # 排他的論理和 {1, 2, 5, 6}
集合型を使う場面というのはこういう演算を使うことを期待している場合で、逆にそれ以外だと上のように「重複を除く」などで使う可能性がある以外には、あまり使いみちはありません。
要素の追加はaddメソッドを使います。取り除くときはremoveかdiscardを使うことになるでしょう(存在しない要素に対して行った場合にエラーになるかどうかだけが異なります)。listの操作とはまったく互換性がないので、注意してください。appendは使えません。
>>> s = set() >>> s.add(1) >>> print(s) {1} >>> s.discard(1) >>> print(s) set()
dictのキーと同じく、中に入れられるのはimmutable(厳密にはhashableであればよいが)だけです。
>>> s = {[1, 2, 3], [4, 5, 6]} Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'list'
内包表記
setには内包表記があります。めったに使わないのですが、dictの内包表記と間違えて書かないために覚えておく必要があります。
>>> s = {i**2 for i in range(5)} >>> print(s) {0, 1, 4, 9, 16}
frozenset
これも知っておいた方が良いでしょう。
集合の中に集合を入れる、あるいはsetを辞書のキーにする、という場面がたまにあります。でも、setはmutableなので、そのままでは使えません。
>>> s = {{1, 2, 3}, {4, 5, 6}} Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'set'
この場合、frozensetが使えます。これも組み込み型です。listに対するtupleだと思えば良いでしょう。
>>> s = {frozenset({1, 2, 3}), frozenset({4, 5, 6})} >>> print(s) {frozenset({1, 2, 3}), frozenset({4, 5, 6})}
まとめ
Pythonの基本的なコレクション型4つについて説明してみました。
この記事の内容程度のことを知っておくと、コーディングがはかどります。