5. 便利な組み込み型

Pythonの強みは言語そのものよりも,その強力なライブラリ群にあると言える.ここではPython本体に組み込まれているオブジェクト(データ型)の中でも最も頻繁にお目にかかるであろうもののうちいくつかを簡単に紹介する.ここで紹介するのはあくまで基本中の基本なので,本格的に使うにはこれだけでは少々心もとないが,これをマスターしておけばあとは各自で調べることができるであろう.

5.1. tuple

tupleと呼ばれるオブジェクトは配列のように複数の値をひとまとめにしたものである.後述のlistと同様に,とりあえずはCやFortranの配列と同じようなものとして考えてもらって構わない.

tupleは複数の値を (), (カンマ)で以下のように表される

1>>> (1, 2, 3)
2(1, 2, 3)

これは3つの整数要素から成るtupleを表す.

配列とは異なり,tupleの要素として任意のPythonオブジェクトを格納できる.また () は空の(要素が何もない)tupleを表す.なお,tupleの各要素は [] で参照することができる.これらを以下の例で確かめてみよう.

1>>> ()                           # 空のtuple
2()
3>>> a = ('this', 'is', 'tuple')  # tupleオブジェクトを変数aに格納
4>>> (1, 'string', a)             # 異なる型のオブジェクトも格納できる
5(1, 'string', ('this', 'is', 'tuple'))
6>>> a[0]                         # aの最初の要素を参照
7'this'

tupleの大きな特徴は 要素の値を変更することができない という点である.要素の値を書き換えようとすると以下のようなエラーとなる.

1>>> a[0] = 'that'
2---------------------------------------------------------------------------
3TypeError                                 Traceback (most recent call last)
4<ipython-input-28-900cc7775085> in <module>
5----> 1 a[0] = 'that'
6
7TypeError: 'tuple' object does not support item assignment

tupleは複数の値を一時的にまとめて扱いたいときによく使われるものなので,その要素を書き換えることは想定されていないのである.典型的な使い方の一つとして,関数から複数の値を返したいときなどに使われる.例えば以下の test_tuple は与えられた2つの引数の和と差を返す関数である.

 1>>> def test_tuple(a, b):
 2...     return a+b, a-b
 3...
 4>>> test_tuple(1, 2)
 5(3, -1)
 6>>> c, d = test_tuple(20, 10)
 7>>> c
 830
 9>>> d
1010

この例のように,実はtupleを作成するときの () は省略することができる. ここでは return a+b, a-b でサイズ2のtuple (a+b, a-b) を関数の返値としている.また,関数の返値としてtupleが返されるときは,その要素をそれぞれ別の変数に代入して使うことができる(この例では c, d).なお,これらの変数はtupleではないので,当然変更も可能である.

5.2. list

基本的にはlistは要素の書き換えができるtupleと考えてよい.tupleの例をそのままlistに置き換えてみよう.

 1>>> []
 2[]
 3>>> a = ['this', 'is', 'list']
 4>>> [1, 'string', a]
 5[1, 'string', ['this', 'is', 'list']]
 6>>> a[0]
 7'this'
 8>>> a[0] = 'that' # 自由に要素の書き換えができる
 9>>> a
10['that', 'is', 'list']

listはCやFortranの配列と異なり各種メソッドを呼び出すことで値の挿入,追加,削除などが自在にできる.使い方は以下の例を見れば明らかだろう.

 1>>> a = [1, 2, 3]             # 初期化
 2>>> a.append(4)               # 最後に新しい要素を追加
 3>>> a
 4[1, 2, 3, 4]
 5>>> a.insert(1, 100)          # 1番目に要素100を追加
 6>>> a
 7[1, 100, 2, 3, 4]
 8>>> a.remove(100)             # 要素100を削除(複数ある場合は最初に見つかった要素)
 9>>> a
10[1, 2, 3, 4]
11>>> a.pop()                   # 最後の要素削除
124
13>>> a
14[1, 2, 3]
15>>> a.extend([10, 11, 12])    # 引数に受け取ったリストの各要素を追加
16>>> a
17[1, 2, 3, 10, 11, 12]
18>>> a.clear()                 # 空にする
19>>> a
20[]

注意すべきはlistのサイズを伸ばす append(), extend() を多用すると一般的には遅くなる可能性が高いということである.基本的には大規模なデータの格納にはlistは使わない [1] ,また何らかの理由で使わざるを得ないときにはサイズが予め分かっているのであれば最初にそのサイズのlistを作っておく(サイズを変更しない),という方針にすべきである.

また,以下のようにtupleをlistに変換することもできる.

1>>> list((3, 4, 5)) # tuple (3, 4, 5) をlistに変換
2[3, 4, 5]

なお,任意のサイズの初期化されたlistは簡単に作ることができる.例えば以下の例は0で初期化されたサイズ10のlistを作る例である.ループで append() を繰り返して作るという無駄なことは絶対にやめよう.

1>>> [0]*10
2[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

listを生成するときに「内包表記」を使うと便利なことも多い.例えば以下のように用いる.ただし,慣れるまでは難しいかもしれないので無理して使う必要はない.

1>>> [i**2 for i in range(5)]
2[0, 1, 4, 9, 16]

5.3. range

これまでも for ループで繰り返し回数を指定するときに for i in range(10) のような書き方をしてきた.rangeは整数列を表すという点を除けば,listやtupleと同じように値の列を表すオブジェクトである.(従ってtuple, list, rangeを総称してシーケンス型と呼ぶ.)

rangeは規則的な数列を表すオブジェクトであるため実際にはメモリ上に値を保持せず,必要に応じて値を計算する.すなわち,tupleやlistと異なり,常に使用するメモリは一定であるという特徴がある.(したがって for ループなどでの使用に向いている.)

必要であれば,以下のようにlistやtupleに簡単に変換することが可能である.

1>>> list(range(5))
2[0, 1, 2, 3, 4]
3>>> tuple(range(5))
4(0, 1, 2, 3, 4)

5.4. dict

dictは辞書(dictionary)や連想配列(ハッシュ)などと呼ばれるオブジェクトである.複数の値を保持するという意味ではlistなどのオブジェクトと同様である.大きな違いは,listを始めとするシーケンス型は要素が順番を持ち,整数添字で各要素を指定するのに対して,dictの要素は順番を持たない.その変わりに文字列の「キー」と対応する「値」のペアで要素を記憶するのがdictオブジェクトである.dictは多くの値を保持しなければいけないときにも配列やリストのように「順番」を気にせず使うことができるので大変便利である.

以下で簡単な使い方を見てみよう.まずはキーと値のペアからdictオブジェクトを作成する.

1>>> a = {'key1' : 'value1', 'key2' : 'value2', 'key3' : 'value3'}
2>>> a
3{'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
4>>> b = dict(key1='value1', key2='value2', key3='value3')
5>>> b
6{'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}

この例ではどちらとも中身は同じもので, key1key2key3 がキー, value1value2value3 がそれぞれのキーに対応する値である.なお,値を一意に指定するためにdictのキーはユニークである必要がある.そのためキーの重複は許されず,仮に重複があった場合には一番最後に指定された値で上書きされることになるので注意しよう.

具体的に要素にアクセスしたり,追加,削除は以下のように行えばよい.

 1>>> a['key3']            # キーを指定して値にアクセス
 2'value3'
 3>>> a['key1'] = 'hoge'   # キーを指定して値を更新
 4>>> a
 5{'key1': 'hoge', 'key2': 'value2', 'key3': 'value3'}
 6>>> a['key0'] = 'value0' # 新たなキーを指定して値を追加
 7>>> a
 8{'key1': 'hoge', 'key2': 'value2', 'key3': 'value3', 'key0': 'value0'}
 9>>> del a['key1']        # 削除
10>>> a
11{'key2': 'value2', 'key3': 'value3', 'key0': 'value0'}

なお,存在しないキーを指定して値にアクセスするとエラーとなる.

1>>> a['key1']
2---------------------------------------------------------------------------
3KeyError                                  Traceback (most recent call last)
4<ipython-input-26-fbbb4b4f3c5e> in <module>
5----> 1 a['key1']
6
7KeyError: 'key1'

実際にはエラーとならないように処理が必要である.キーが存在するかどうかは in 演算子でチェックすることができるし,キーが存在しない要素にアクセスした時に「デフォルト値」を返すような処理も有効である.以下で例を見てみよう.

1>>> 'key1' in a
2False
3>>> 'key2' in a
4True
5>>> a.get('key1', 'hoge')
6'hoge'
7>>> a.get('key2', 'hoge')
8'value2'

1行目と3行目はそれぞれキーがあるかどうかをチェックしている.5行目および7行目は get() メソッドを使ってキーに対応する値にアクセスしているが, このとき2番目の引数にデフォルト値を指定することができる(指定しない場合は None). get() でアクセスした場合には [] とは異なり,キーが見つからなかったときにエラーを出さずに,デフォルト値を返す.これによりエラーチェック処理を省いたり簡単にすることができる.

dict全体を順番に操作する処理には keys()values()items() といったメソッドを用いることになる.それぞれキー,値,キーと値のペアの「メモリビュー」オブジェクトを返す.

1>>> a.keys()
2dict_keys(['key2', 'key3', 'key0'])
3>>> a.values()
4dict_values(['value2', 'value3', 'value0'])
5>>> a.items()
6dict_items([('key2', 'value2'), ('key3', 'value3'), ('key0', 'value0')])

ここでは「メモリビュー」が何かといった細かいことは置いておいて,具体的な使い方の例を見てみよう.これらは以下のようにループで用いることになるだろう.

 1# 全てのキーと値のペアを出力
 2>>> for key, val in a.items():
 3...    print("key = {:5s} : value = {:5s}".format(key, val))
 4key = key2  : value = value2
 5key = key3  : value = value3
 6key = key0  : value = value0
 7# 全てのキーを出力
 8>>> for key in a.keys():
 9...    print(key)
10key2
11key3
12key0

例えばキーをソートしたいことがあるかもしれない.dict_keysオブジェクトは直接ソートすることができないが,以下のようにlistに一度変換してしまえば簡単にソートができる.

 1>>> keys = list(a.keys())  # dict_keysをlistに変換
 2>>> keys
 3['key2', 'key3', 'key0']
 4>>> keys.sort()            # listオブジェクトなのでソートできる
 5>>> keys
 6['key0', 'key2', 'key3']
 7>>> for key in keys:
 8...    print("key = {:5s} : value = {:5s}".format(key, a[key]))
 9key = key0  : value = value0
10key = key2  : value = value2
11key = key3  : value = value3

5.5. 第5章 演習課題

注釈

以下の課題はJupyter Notebookの使用を前提としているが,もちろん他の実行環境でも同等の処理を実現出来ていれば問題ない.

5.5.1. 課題1

サンプルを実行して動作を確認しよう.

5.5.2. 課題2

tupleの便利な使い方として関数の引数の扱いが挙げられる.

1>>> def f(*args):
2...     print(args) # 全ての引数を出力

のように引数に * を指定すると,それ以降に与えた引数は全てtupleとして引数(この場合は args )に格納される.これは多数のあらかじめ個数の分からない引数を受け取るために使われる.これは位置指定引数(positional argument)などと呼ばれる.

任意の個数・任意の型の引数を受け取り,受け取った全ての引数を出力する print_args 関数を作成せよ.例えば以下のような結果が得られればよい.Pythonの任意のオブジェクトを文字列表現に変換する str 関数を用いること.

1>>> print_args('hello', (1, 2, 3), [], {'key' : 'val'}, None)
2args[  0] = hello
3args[  1] = (1, 2, 3)
4args[  2] = []
5args[  3] = {'key': 'val'}
6args[  4] = None

なお,あらかじめ定義されているtupleを関数に渡すときに * を使って

1>>> a = (0, 1, 2)
2>>> print_args(*a)

のように渡すこともできる.これも使えるようになると便利である.

5.5.3. 課題3

任意のlistを受け取りそこから重複するものを除いた新たなlistオブジェクトを返す関数 unique を作成せよ.ただし重複するもの以外の順番は保持するものとする.例えば以下のような結果が得られればよい.(やり方は色々あるが素直にループで処理する方法を考えてみよう.)

1>>> unique(['a', 1, 'b', 2, 'c', 1, 2, 3, 'b', 'd', 'a', 3])
2['a', 1, 'b', 2, 'c', 3, 'd']

5.5.4. 課題4

dictは関数のキーワード引数を受け取るときに用いると便利である.

1>>> def f(**kwargs):
2...     print(kwargs)

のように引数に ** を指定すると,それ以降の任意のキーワード引数がdictとして引数(この場合 kwargs )に格納される.

任意個数のキーワード引数の引数名(キー)とその値を出力する関数 process_kwargs を作成せよ.ただし,引数 a, b は必ず与えられるものとし,もし与えられなかった場合にはデフォルトでNoneを出力するものとせよ.また引数名で辞書順にソートして出力せよ.

1>>> process_kwargs(a='hi', c='chao', x=10, y=20, z=30)
2a hi
3b None
4c chao
5x 10
6y 20
7z 30

なお,あらかじめ定義されているdictを関数に渡すときに ** を使って

1>>> a = {'a' : 'hi', 'c' : 'chao', 'x' : 10, 'y' : 20, 'z' : 30}
2>>> print_kwargs(**a)

のように渡すこともできる.さらに,位置指定引数と合わせて

1>>> def f(*args, **kwargs):
2...     print(args)
3...     print(kwargs)

のように使うことも可能である.注意すべきは必ず 位置指定引数の方がキーワード引数より先になければならない という点である.

5.5.5. 課題5

JSON(JavaScript Object Notation)は特にウェブを介したデータのやり取りによく使われるデータの記述形式である.例えば以下は JSON GENERATOR というサイトで作ったダミーのJSONデータの抜粋である.このようにJSONはPythonのlistとdictを組み合わせたような形式で書かれている.

 1[
 2  {
 3    "age": 28,
 4    "name": {
 5      "last": "Morales",
 6      "first": "Hickman"
 7    },
 8    "email": "hickman.morales@electonic.cg",
 9    "index": 0,
10    "company": "Electonic",
11    "favoriteFruit": "apple"
12  },
13  {
14    "age": 59,
15    "name": {
16      "last": "Norris",
17      "first": "Emerson"
18    },
19    "email": "emerson.norris@recrisys.sj",
20    "index": 1,
21    "company": "Recrisys",
22    "favoriteFruit": "strawberry"
23  },
24  {
25    "age": 47,
26    "name": {
27      "last": "Garza",
28      "first": "Shelly"
29    },
30    "email": "shelly.garza@signity.mw",
31    "index": 2,
32    "company": "Signity",
33    "favoriteFruit": "orange"
34  },
35  {
36    "age": 30,
37    "name": {
38      "last": "Owens",
39      "first": "Rhoda"
40    },
41    "email": "rhoda.owens@kidgrease.cn",
42    "index": 3,
43    "company": "Kidgrease",
44    "favoriteFruit": "strawberry"
45  },
46  {
47    "age": 18,
48    "name": {
49      "last": "Dodson",
50      "first": "Florence"
51    },
52    "email": "florence.dodson@musanpoly.cy",
53    "index": 4,
54    "company": "Musanpoly",
55    "favoriteFruit": "banana"
56  }
57]

このようなJSON形式のデータはPython標準の json モジュールを用いることで非常に簡単に扱うことができる.ここでは実際にこのJSON文字列データからfirst nameとlast nameおよびemailを取り出して表示する関数 process_json1 を作成せよ.この例のJSON文字列をコピーアンドペーストで json_string という変数に格納し,実行すると以下のような結果が得られる.

1>>> process_json1(json_string)
2Hickman MORALES <hickman.morales@electonic.cg>
3Emerson NORRIS <emerson.norris@recrisys.sj>
4Shelly GARZA <shelly.garza@signity.mw>
5Rhoda OWENS <rhoda.owens@kidgrease.cn>
6Florence DODSON <florence.dodson@musanpoly.cy>

注釈

json.loads(json_string) によってJSON文字列をlistに変換することができる.listの各要素がdictオブジェクトになっているので,それらを逐一処理していけばよい.

5.5.6. 課題6

課題5で扱ったJSON文字列から個人を特定できる情報を削除したJSON文字列を返す関数 process_json2 を作成せよ.例えば以下のような結果が得られればよい.

 1>>> print(process_json2(json_string))
 2[
 3  {
 4    "age": 28,
 5    "index": 0,
 6    "company": "Electonic",
 7    "favoriteFruit": "apple"
 8  },
 9  {
10    "age": 59,
11    "index": 1,
12    "company": "Recrisys",
13    "favoriteFruit": "strawberry"
14  },
15  {
16    "age": 47,
17    "index": 2,
18    "company": "Signity",
19    "favoriteFruit": "orange"
20  },
21  {
22    "age": 30,
23    "index": 3,
24    "company": "Kidgrease",
25    "favoriteFruit": "strawberry"
26  },
27  {
28    "age": 18,
29    "index": 4,
30    "company": "Musanpoly",
31    "favoriteFruit": "banana"
32  }
33]

注釈

json.dumps(object, indent=2) によって与えられたオブジェクトをJSON文字列に変換することができる.ここで引数indentに与える数値は文字列にしたときのインデント数である.