167
105

More than 3 years have passed since last update.

その[カッコ]、本当に必要?

Last updated at Posted at 2019-11-20

イントロダクション

👇のような巨大なリストがあったとする。

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 を生成する関数を豊富に揃えている。

167
105
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
167
105