イントロダクション
👇のような巨大なリストがあったとする。
big_list = [
{"name": "John", "age": 20},
{"name": "Mary", "age": 17},
{"name": "Tom", "age": 16},
{"name": "Michael", "age": 11},
# ...
]
name
をすべて結合したいとき、👇のようなコードを書く人が少なくないだろう。
# ↓ ↓
names = [per["name"] for per in big_list] # list comprehension
all_names = ", ".join(names)
この[ ]
は実は不要である。不要なだけではなく、big_list
と同じサイズのリストが作られ、メモリを無駄に消費することになる。本来の目的はname
をすべて結合することなので、このような中間的なリストは必要ない。
単に👇のように[ ]
を外すだけで(シンタックスの都合上( )
が必要)、無駄なリストは作られなくなる。
names = (per["name"] for per in big_list) # generator expression
all_names = ", ".join(names)
list comprehension
list comprehension は、実行された時点でリストが作成される。
[i * i for i in [1, 2, 3]]
generator expression
generator expression は generator オブジェクト(iterator) を返す。実行された時点では元のリストに対しては要素へのアクセスも何もされない。
(i * i for i in [1, 2, 3])
iterator とは next
関数で一つづつ値を取り出すことができるオブジェクトである。この generator オブジェクトに関しては、next
で次の値をリクエストされたときにはじめて元のリストの要素を一つ取り出し、必要な処理を行って返すようになっている。そのため、元のリストがいくら巨大でも generator が消費するメモリサイズは一定である。このような処理の仕方は lazy evaluation と呼ばれる。
def f(x):
print(f"x={x}")
return x
gen = (f(i) for i in [1, 2, 3])
next(gen) # x=1
next(gen) # x=2
next(gen) # x=3
next(gen) # Exception has occurred: StopIteration
上の例ではリストから generator を作ったが、generator から generator を作ることもできる。
gen1 = (i * i for i in range(10))
gen2 = (i for i in gen1 if i > 10)
注意すべき点として、iterator は一度しか反復できないということである。一度最後まで読んでしまうと、空のリスト同然になる。
gen = (i * i for i in [1, 2, 3])
print(sum(gen)) # 14
print(sum(gen)) # 0
そのため、複数回反復する必要がある場合は 毎回 generator を作るか list comprehension を使うなどする必要がある。
generator(iterable) が使える場所
iterator は iterable なので(iterable は必ずしも iterator ではない)、iterable が使える場所ならばどこでも generator expression が使えるということである。Python の構文やビルトイン関数では iterable が使える場所は意外なほど多い。
-
ビルトイン関数
-
all
,any
-
enumerate
,zip
,filter
,map
-
sum
,max
,min
,sorted
-
list
,dict
,set
,tuple
-
for-loop
for x in iterable:
print(x)
- comprehensions
# list
[i for i in iterable]
# set
{i for i in iterable}
# dict
{i : i for i in iterable}
- unpacking
# list
[1, 2, *iterable1, *iterable2]
# set
{*iterable, 3}
# 引数
print(*iterable)
# 代入
a, b, c = iterable
ls[3:6] = iterable
-
in
演算子
3 in iterable
- ビルトイン型のメソッド
# str
"".join(iterable)
# set
{1, 2, 3}.union(iterable) # intersection, difference などもOK
# dict
{"name": "John"}.update(iterable)
他にも、標準ライブラリ itertools が iterable
から iterator
を生成する関数を豊富に揃えている。