Python 3.0 Hacks

第8回リスト処理と内包表記

リスト処理関数の仕様変更

Python3.0では、リスト処理に関わる多くの関数/メソッドに変更が加えられました。また、リスト処理を行う際に非常に便利なリスト内包表記にも追加・変更が加えられています。今回は、それらの関数/メソッド/構文について書きたいと思います。

iteratorへの変更

まずは、リスト1のソースをPython2とPython3で実行してみてください。

リスト1 リスト処理関数
print(range(10)) # range
print(map((2).__mul__, range(10))) # map
print(filter(lambda x: x % 2, range(10))) # filter
print(zip(range(5), range(5, 10))) # zip

d = dict(aa=100, bb=300)
print(d.keys()) # dict.keys
print(d.values()) # dict.values
print(d.items()) # dict.items

結果は次のリスト2、3のようになります。

リスト2 python2.6での実行結果
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
[1, 3, 5, 7, 9]
[(0, 5), (1, 6), (2, 7), (3, 8), (4, 9)]
['aa', 'bb']
[100, 300]
[('aa', 100), ('bb', 300)]
リスト3 python3.0での実行結果(例)
range(0, 10)
<map object at 0xb7bdd74c>
<filter object at 0xb7bdd74c>
<zip object at 0xb7bdd74c>
<dict_keys object at 0xb7bbaac4>
<dict_values object at 0xb7bbaac4>
<dict_items object at 0xb7bbaac4>

Python2.6では、上に挙げたそれぞれの関数、メソッドはそれぞれリストを返しています。しかし、Python3.0ではリストではない値になっています。これは、それぞれの関数がリストを返すのではなく、 iterator objectを返すように挙動が変更されたためです。iteratorを返すことで、一度に全要素を計算せず、要素が必要とされる時に計算するようになります。これにより、使用するメモリ量を低く抑えることができるようになっています。

2.xと同様の結果が必要なときには、 list関数にiteratorを渡してリストを生成することで同等の結果を得られます。

これら関数/メソッドの仕様変更により、 2.xでiteratorを生成するために存在した同等機能の関数が廃止されています。仕様が変わった関数と廃止された関数の一覧を表1にまとめました。

表1 Python 3.0から仕様が変わった関数
iteratorを返すようになった関数 廃止された関数
range xrange
map itertools.imap
filter itertools.ifilter
zip itertools.izip
dict.keys dict.iterkeys
dict.values dict.itervalues
dict.items dict.iteritems

その他のリスト処理関数

iteratorを返すように変更された上記関数以外にも、変更された関数があります。

list.sortとsorted関数は、ソートを行う際の比較関数としてcmpキーワード引数を取っていましたが、廃止され、keyキーワード引数を使用するようになっています。この変更は、要素の比較のたびに関数を呼び出すよりも、比較のための値を全要素に対して生成する方が処理が速いためです。

それともう一つ、map、filterに並ぶ代表的な高階関数である、畳み込み演算を行うためのreduce関数は、3.0で組み込み関数から外され、functoolsモジュールの中に取り込まれました。

内包表記

リスト処理関数以外にリスト処理を行う際に使われるのが、リスト内包表記です。内包表記はmap+filterの処理を一度に行うことができるうえ、非常に柔軟に処理を記述することができるため、多用する方もいるのではないでしょうか。Python 3ではリスト内包表記にも仕様の追加、変更が加えられました。

カウンタ変数のスコープ

まずはリスト4の例を実行してみてください。

リスト4 内包表記の例
x = 'some string'
print(x)
[x for x in range(100)]
print(x)

Python2.6, Python3.0それぞれで実行した結果はリスト5、6の通りです。

リスト5 Python2.6での実行結果
some string
99
リスト6 Python3.0での実行結果
some string
some string

Python2.6では、内包表記内部で使われたカウンタ変数 x のスコープは、内包表記の外と同じになっていて、内包表記の外で使っている変数 x の値が書き換わってしまっています。Python3.0では内包表記のカウンタ変数のスコープが内包表記の内部のみになったため、このような挙動はしなくなっています。

この挙動はリスト内包表記を使った時のみの挙動で、generator式の場合はPython2であっても外のスコープに影響及ぼしません。そのため、リスト7のようなコードを実行しても、print(x)で出力される x の値は最初にセットした 'some string' のままです。

リスト7 generator式とスコープ
x = 'some string'
print(x)
list(x for x in range(100))
print(x)

内包表記の追加

Python2.xでは、内包表記はgenerator式を含めると2つでした。これが、Python3ではset, dict用の内包表記が追加され、4つに増えています。追加された構文の例と、Python2における同等の記述をあわせてリスト8、9に示しました。

リスト8 set
# set を生成する内包表記
{x for x in iterable if x is not None}

# 2.x では以下のように書けば同様の結果を得られる
set(x for x in iterable if x is not None)
リスト9 dict
# dict を生成する内包表記
{x:func(x) for x in iterable}

# 2.x では以下のように書けば同様の結果を得られる
dict((x, func(x)) for x in iterable)

内包表記は非常に便利ではありますが、多用しすぎたり、入れ子にしたりすると途端に読みにくくなってしまいます。Pythonは、読みやすさを重視したプログラム言語なので、あまりやり過ぎない程度に使っていくと良いのではないかと思います。

まとめ

今回は、Pythonのプログラムでよく使われるリスト処理に関する関数や構文を扱いました。Python3になり、各種リスト処理の関数が iterator を返すようになり、メモリ使用効率が良くなったり、内包表記がさらに拡張され、便利になりました。プログラムにおいて繰り返し処理は基本となる部分なので、この変更でPythonがより使いやすくなったのではないでしょうか。

おすすめ記事

記事・ニュース一覧