[Python]内包表記と条件演算式

2016年11月28日月曜日

python

Pythonでの条件演算子の書き方を知らなかったので、面食らった話。

リスト内包表記で奇数の2乗を生成しようとすると以下になる。
>>> [ x ** 2 for x in xrange(10) if x % 2 != 0 ]
[1, 9, 25, 49, 81] 

それに対して、偶数の場合はそのまま、奇数の場合は2乗を生成しようとすると以下になる。
>>> [ x ** 2 if x % 2 != 0 else x for x in xrange(10) ]
[0, 1, 2, 9, 4, 25, 6, 49, 8, 81]

なんと、ifを書く位置が変わってしまうのだ!
いやいや、ちょっと待って。。。そういう話ではなかったんだ。

Python言語リファレンス 5.11. 条件演算 (Conditional Expressions)
によるとPythonでCなんかである条件演算子(x ? y : z; 形式のやつ)を使おうとすると以下の形式の式を使うことになる。
y if x else z
これを条件演算式と呼んでいるようだけど、まずxが評価されて、それが真であればyが返る。偽であればzが返る。
式の字面からすれば、その通りだけど左から順に読んでいくC形式と違うので、注意が必要だ。

リスト内包表記ではforの後ろに置かれたifの条件に合致するもののみを対象要素としてリストに入れてくれる。
Python言語リファレンス 5.1.4. リストの内包表記

だから、forの前に書かれているifは条件演算式によるもので、リスト内包表記の記法とは別個のものだったんだね。

条件演算式をネストすると、以下のようなFizzBazz問題の書き方もできる。
まあ、コードが見やすいかどうかは別の話だけど。。。
>>> [ 'FizzBazz' if x % 15 == 0 else 'Fizz' if x % 3 == 0 else 'Bazz' if x % 5 == 0 else x for x in xrange(1, 31) ]
[1, 2, 'Fizz', 4, 'Bazz', 'Fizz', 7, 8, 'Fizz', 'Bazz', 11, 'Fizz', 13, 14, 'FizzBazz', 16, 17, 'Fizz', 19, 'Bazz', 'Fizz', 22, 23, 'Fizz', 'Bazz', 26, 'Fizz', 28, 29, 'FizzBazz']