下手の横好き

文系卒エンジニアのしがない技術ブログ

Pythonの内包表記について調べた話

動機

近頃、業務でAWS(特にLambda)を使っている。

aws.amazon.com

Lambdaで「EC2インスタンスのAMIを定期取得」するためのスクリプトPythonで組もうと思い、色々と調べていた時の事。

qiita.com

上記記事で見慣れない表記を発見。

return sum([
    [instance for instance in reservation['Instances']]
    for reservation in reservations
], [])

sum()関数は、イテレータブルなオブジェクトの総和を求める関数。
見慣れないのはこの部分。

[instance for instance in reservation['Instances']] for reservation in reservations

リスト内包表記とは

公式ドキュメントは以下。
5. データ構造 — Python 3.6.5 ドキュメント

リスト内包表記はリストを生成する簡潔な手段を提供しています。
主な利用場面は、あるシーケンスや iterable (イテレート可能オブジェクト) のそれぞれの要素に対してある操作を行った結果を要素にしたリストを作ったり、ある条件を満たす要素だけからなる部分シーケンスを作成することです。

基本的な記法

通常のリスト生成はこんな感じ。

list = []

for i in range(10):
  list.append(i)

list
# >>> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

一方、リスト内包表記を使うとこうなる。

list = [i for i in range(10)]

list
# >>> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

リスト内包表記の基本的な記法は以下。

[i for i in iterator]

if節の使用

リスト内包表記は、括弧の中の 式、 for 句、そして0個以上の for か if 句で構成されます。 とある。

通常記法:

list = []

for i in range(10):
  if i % 2 == 0:
    list.append(i)

list
# >>> [0, 2, 4, 6, 8]

リスト内包表記:

list = [i for i in range(10) if i % 2 == 0]

list
# >>> [0, 2, 4, 6, 8]

複数のfor節の使用

2つのリストから要素を操作することが可能。
以下のコードでは、2つのリストからそれぞれ値の違うもの同士をペアにしている。

[(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
# >>> [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

パフォーマンス

iPython%%timeitで計測してみる。

通常記法:

list = []

for i in range(10000):
  list.append(i)

# >>> 1000 loops, best of 3: 1.08 ms per loop

リスト内包表記:

list = []
list = [i for i in range(10000)]

# >>> 1000 loops, best of 3: 337 µs per loop

うむ。速い。

これらを踏まえて

もう一度こちらを見てみる。

[instance for instance in reservation['Instances']] for reservation in reservations

この記法は通常記法にすると

for reservation in reservations:
  for instance in reservation['Instances']:
    # instanceに対する処理

と読める。理解が深まった。

いろいろな内包表記

Pythonの内包表記には、リスト内包表記の他にも

  • 辞書型内包表記
  • ジェネレータ型内包表記
  • 集合型内包表記

等があるらしい。奥が深い。
(友人の記事。感謝。)

qiita.com

ジェネレータや集合(set)については、Pythonに於けるイテレータを改めて学んだ後にまとめたいと思う。
(まだまだPython初心者のため、使ったことがない) 今回は使ったことのある辞書についてのみ記述する。

辞書型内包表記

任意のキー:値のペアを作ることが可能。

{key: val for key, val in zip(range(3), ['hoge','fuga','piyo'])}
# >>> {0: 'hoge', 1: 'fuga', 2: 'piyo'}

zip()関数と相性がいいみたい。どこかで使い所はありそう。

まとめ

いいところ

  • ワンライナーで記述出来る
  • 通常記法よりもパフォーマンスがいい
  • ifif elseと併用出来、対応力が高い。

工夫次第なところ

  • 可読性の担保
  • map()filter()との使い分け

PythonRubyは使ってて刺激が多い言語だと改めて感じた。
これから色々試していきたい。
以上。

参考

qiita.com

qiita.com

qiita.com