はじめに
この記事を開いた人の大半は「itemgetter? なにそれ」という反応でしょう。
(いや、検索で来た人はそうでもないかもしれないけど)
itemgetterは以下のように使えるものです。
>>> lst = list(zip([1,3,5,6,7,1,4], [3,4,1,0,8,5,2])) # 特に値に意味はない >>> sorted(lst, key=lambda x:x[0]) [(1, 3), (1, 5), (3, 4), (4, 2), (5, 1), (6, 0), (7, 8)] >>> sorted(lst, key=lambda x:x[1]) [(6, 0), (5, 1), (4, 2), (1, 3), (3, 4), (1, 5), (7, 8)] >>> from operator import itemgetter >>> sorted(lst, key=itemgetter(0)) [(1, 3), (1, 5), (3, 4), (4, 2), (5, 1), (6, 0), (7, 8)] >>> sorted(lst, key=itemgetter(1)) [(6, 0), (5, 1), (4, 2), (1, 3), (3, 4), (1, 5), (7, 8)]
つまり、itemgetter(0)はlambda x:x[0]と等価です。
参考:
10.3. operator — 関数形式の標準演算子 — Python 3.6.5 ドキュメント
では、どちらを使うべきなのでしょうか?
目次
可読性とタイプ数
可読性
可読性は正直なんとも言い難いです。
とりあえずlambda式はlambda式の概念さえ理解していればわかるともいえますし、それでもわかりづらいという人もいるでしょう。また、見た目はグロいです。
itemgetterは見た目はすっきりしています。これはとても大切なことで、lambdaまみれで入り組んだコードはもううんざりです。ただ、itemgetter自体を知らない人は恐らく多いので、初見では「helpを見る」「ググる」という作業が発生します。また、何をやっているか明示的にわかりづらいので、lambdaの方がマシという考え方もあります。
タイプ数
タイプ数的には、はじめにを見て頂ければわかる通り互角です。itemgetterという途方もなく長い識別子のせいで、lambda式に勝てません。
一応、asで別名にしてしまうという反則技(?)はあります。
from operator import itemgetter as get # もうちょっと良い名前を考えて
こうするとタイプ数的にはlambdaに勝てそうです。ただ、こうするのは如何なものか? と相成ります。混乱を招きそうなので、あまりやりたくない手です。
結論
可読性・タイプ数では決められない。
速度差
まさかこんなのに速度差なんてねーだろ、そう思っていた時期が私にもありました。
都合によりminで比較。こうすると常に全数線形探索されるはずなので、再現性等が良いです。
>>> import timeit >>> lst = list(zip(range(10000), range(10000))) >>> timeit.timeit(lambda :min(lst, key=lambda x:x[0]), number=1000) 1.33255122898845 >>> timeit.timeit(lambda :min(lst, key=itemgetter(0)), number=1000) 0.6424821490072645
倍違うのだった。えぇ・・・
怪しいので、key関数を外部に定義してみます。
>>> key1 = lambda x:x[0] >>> key2 = itemgetter(0) >>> timeit.timeit(lambda :min(lst, key=key1), number=1000) 1.32674505902105 >>> timeit.timeit(lambda :min(lst, key=key2), number=1000) 0.6525059329869691
変わらず。
結論
速いので、特にこだわりがなければitemgetterを使いましょう。
私はわざわざimportするのがダルいのと、個人的にlambdaがそれなりに好きという理由でlambdaを使い続けると思います。競プロとかのときはitemgetter有利なんじゃないでしょうか。