nikkie-ftnextの日記

イベントレポートや読書メモを発信

Pythonの内包表記のifの位置

こういう見方ができるな〜と気づいたのでしたためます。

目次

Pythonの内包表記

Pythonの文法には内包表記(comprehension)なるものがあります。
リスト、集合、辞書は内包表記でも書けます。

チュートリアルより、リスト内包表記の例

5.1.3. リストの内包表記より

平方(2乗)のリストを作る例です。
for文で作ると

これはループが終了した後にも存在する x という名前の変数を作る (または上書きする) ことに注意してください。

(この記事のコードはPython 3.11.4で動作確認しています)

>>> squares = []
>>> for x in range(10):
...   squares.append(x**2)
...
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> x
9

チュートリアルでは「平方のリストをいかなる副作用もなく計算」する方法として2つ紹介しています。

>>> # インタプリタを一度終了してから起動しています
>>> squares = list(map(lambda x: x**2, range(10)))
>>> squares = [x**2 for x in range(10)]

この2つは変数xを作る副作用がありません

>>> x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined

後者がリスト内包表記ですね。

これはより簡潔で読みやすいです。

用語集より

チュートリアルでリスト内包表記を見た後は、用語集に当たると他の内包表記の例が確認できます

言語リファレンス 式より

チュートリアルにあった

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

という点を深掘りしましょう。

言語リファレンスの6.2.4. リスト、集合、辞書の表示より

内包表記 (comprehension) と呼ばれる、ループ処理とフィルター処理の組み合わせを用いた計算結果

内包表記はまず単一の式、続いて少なくとも 1 個の for 節、さらに続いて 0 個以上の for 節あるいは if 節からなります。

PEG1による内包表記の構文定義を確認すると、内側にネストできる2ことを表現していますね(comp_iter -> comp_if -> comp_iter -> ...)

内包表記のifの位置に注目する

forより後のif

言語リファレンスに「ループ処理とフィルター処理の組み合わせ」とありました。

  • forがループ処理
  • forの後のifがフィルタ処理(条件を満たす要素だけを取り出す
>>> # 用語集にある例
>>> result = ['{:#04x}'.format(x) for x in range(256) if x % 2 == 0]
>>> len(result)
128

つまり、forより後にifを書くと、内包表記に渡したイテラブル3の要素を、条件に沿って選んで繰り返すことになります。
元のイテラブルから要素数が減るわけですね。

ちなみに、forの後のifは複数書けちゃうんです!

forより前のif

内包表記ではforよりにifを書けます。
このforはループ処理で、フィルタのためのifはありません(なので、全要素について繰り返し)

forより前のifは何かというと、リファレンスでいう「単一の式」を構成するifです。
もうちょっと言うと、「条件式」です。
https://docs.python.org/ja/3/reference/expressions.html#conditional-expressions

条件式 (しばしば "三項演算子" と呼ばれます) は最も優先度が低いPython の演算です。

x if C else y という式は最初に x ではなく条件 C を評価します。 C が true の場合 x が評価され値が返されます。 それ以外の場合には y が評価され返されます。

つまり 条件式(ifを含む) for ... ということです。
イテラブルの全要素について、条件式で評価するわけですね。
フィルタはないので、要素数が減ることはありません。

>>> ["odd" if i % 2 == 1 else "even" for i in range(5)]
['even', 'odd', 'even', 'odd', 'even']

個人的には、条件式の部分を関数に抽出すると読みやすくなることが多いと思います

まとめ:Pythonの内包表記のifの位置

  • 内包表記はforのに(複数の)ifを書ける。これはフィルタとして機能する
  • forの三項演算子としてのifを使った式を書くこともできる(全要素に三項演算子を適用)

同僚とペアプロする中で内包表記を手短に説明して、相手が理解をアウトプットしてくださったので、ifの位置というそれまで考えたことのない着眼点に気づけました。
ありがとうございました。

そういえばこのエントリのタイトルは5・7・5のリズムですね。川柳でした

P.S. 内包表記を使い倒す例が載っていてオススメな記事

過去のPython Weekly(566)で知りました

ここで言及した「forの後の複数のif」があります。
さらに、ウォルラス(:=)の利用、例外の扱い、ループのbreakなど興味深かったです。


  1. Parsing Expression Grammar(解析表現文法)。扱った記事はこちら
  2. 直近学んだCompositeだ!
  3. forと一緒に使えるオブジェクト、と言えるかなと思います。詳しくは