静かなる名辞

pythonとプログラミングのこと


【python】気持ち悪いリスト内包表記のサンプルたち

 最終更新:2018-4-21

 この記事は思いつく度に加筆していこうと思う。

 目次

スポンサーリンク



二回評価されるのを避けたかった

 lstは適当なlist、fは適当な関数、f_condはfの値が適当な条件を満たしているかを判定する関数。

>>> [f(x) for x in lst if f_cond(f(x))]

 f(x)が二回呼ばれるのが気に食わない、というかパフォーマンス的にまずい。for文で書くのが正攻法だと思うが、こうしてみることにする。

>>> [f_x for f_x in [f(x) for x in lst] if f_cond(f_x)]

 f(x)を二回呼ぶのとリストを二回走査するのとでどっちがマシだろうか、という気持ちが何となく募る。

 実はこんなのも考えた。

>>> [x for x in [(lambda x: x if f_cond(x) else None)(f(x)) for x in lst] if x is not None]

 見た目は禍々しいけど、lispのlet的にlambda式を使ってみただけで、やってることは複雑ではない。でもリスト内包表記のこの部分(そういえばなんて呼ぶんだろう・・・)の評価値でfilterするみたいな処理が書けないので、Noneを返してもう一回走査して取り除く羽目に。色々なオーバーヘッドを考えるとこの書き方は不利なだけ。

 なんとか一重リスト内包表記のまま、f(x)の評価を1回で済ませられないか?

  • f(x)をメモ化する

 一つのソリューションだが、この問題においては算数ドリルを電卓で解くかの如き暴挙。なので慎みたい

  • C言語でいうところの「代入文の値」みたいなことを実現する方法をなんとかして考える

 できるの?


 python言語の設計思想は、「そういうことはやるな」なんだろうな。

>>> class Tmp:
...     def __init__(self):
...         self.tmp = None
...     def set(self, value):
...         self.tmp = value
...         return value
...     def get(self):
...         return self.tmp
...
>>> tmp = Tmp()
>>> [tmp.get() for x in lst if f_cond(tmp.set(f(x)))]

 なんかlistのappendがselfを返さない(Noneを返す)話とかと通じるものがあるような気がするといえばするし、単なる思い過ごしのような気もする。オブジェクトの内部状態を弄るメソッドと「関数」は、割と別物なので、混ざらないように気をつけようよ、みたいな雰囲気があるような気はするんだけど。

 で、上のを書いたらもっと酷い書き方も思いついた。

>>> [y for x in lst for y in [f(x)] if f_cond(y)]

 適当なlstとfとf_condを定義して動かしてみたら、正しく動いた。なんだろう、これ。2個めのforを代入というか束縛に使ってるとでも言うべきか。速度は出ないと思う*1。でも一重リスト内包表記という条件は満たしていると言っても良いかも、とか思い始めた。

numpy配列がなんだ!俺がやってやる!

 この前の夜、眠いと思いながら、行列の最大固有値とそれに属する固有ベクトルを求めるプログラムを書いた。速度の要求は一切ないプログラムだった。

 できあがった代物がこれである。コメントは今付けた。

# 行列Tを固有値分解
la, v = np.linalg.eig(T)

# 固有値の大きい順に並べる
tmp = sorted(zip(la, v.T), key = lambda x:x[0], reverse = True)

# 固有値の配列
la_sh = [t[0] for t in tmp]

# 固有ベクトルの配列
v_sh = [t[1] for t in tmp]

# 最大固有値
la1 = np.abs(la_sh[0])

# 最大固有値に属する固有ベクトル・・・なんだこれ
v1 = np.array([np.abs(x) for x in v_sh[0].A[0]])

 こういうのってウ○コード・マニアとかに投稿したら意外とウケたりするの?

forのよさを殺していくスタイル

 やりたいこと:数字のリストがあるとき、n番目の数字とn-1番目,n+1番目の平均を入れたリストを作って欲しいな。
 (要するに移動平均を計算したい)

 ソリューション:

>>> lst = list(range(10))
>>> [sum((lst[i:i+3]))/3 for i in range(len(lst)-2)]
[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]

 コメント:

 前後の数字を参照したければindex使ってアクセスするしかない。

 range(len(lst))とか書くときのコレジャナイ感。

2018/3/30 追記

 こういうときはenumerateを使う手があります。
【python】pythonのenumerateの使い方 - 静かなる名辞

リスト内包表記でファイル出力

 ひょっとしてできるか? と思って書いたらできてしまった。

$ python
>>> [fptr.write(x) if x != "end" else fptr.close() for fptr in [open("hoge.txt", "w")] for x in ["1", "2", "3", "4", "5", "end"]]
>>> Ctrl + D
$ cat hoge.txt
12345

 ファイル入力がもっと簡単にできるのは自明なので省略。
 ということは、リスト内包表記でコンパイラだろうが(テキストベースの)ブラウザだろうが書けるということだろうか。気合さえあれば。

無限ループ

 リスト内包表記を使うと無限ループも作れる。

>>> lst = [None]
>>> [lst.append(None) for x in lst]

 無限ループごときに2行を要するのはアホくさい。lambdaを使って一行にまとめることができる(lispのletのように使う)。

>>> (lambda lst:[lst.append(None) for x in lst])([None])

 一行で無限ループするコードが書けた。ただしこのコードはそのうちメモリの限界にぶち当たって止まるので、厳密には無限ループではない。

リスト内包表記で走査しながらremove

 何が起こっているのかよくわからなかった。

>>> lst = list(range(10))
>>> lst
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> [lst.remove(x) for x in lst]
[None, None, None, None, None]

 printしてみる。

>>> lst = list(range(10))
>>> [(print(lst), lst.remove(x), print(lst)) for x in lst]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 3, 4, 5, 6, 7, 8, 9]
[1, 3, 4, 5, 6, 7, 8, 9]
[1, 3, 5, 6, 7, 8, 9]
[1, 3, 5, 6, 7, 8, 9]
[1, 3, 5, 7, 8, 9]
[1, 3, 5, 7, 8, 9]
[1, 3, 5, 7, 9]
[(None, None, None), (None, None, None), (None, None, None), (None, None, None), (None, None, None)]||<

 やはりよくわからない。確実に言えるのは、こういうことはしない方が賢明ということ。

チューリング完全

 そのうちリスト内包表記で複雑なプログラムを書いてみたいと思っていたのだが。こんなものを見つけてしまった。

リスト内包表記の活用と悪用 - Qiita

 リスト内包表記はチューリング完全という主張である。branf*ckを実装しておられる。楽しいこともたくさん書いてある。

 純リスプを実装した人もいた。

Pythonのリスト内包表記はチューリング完全だから純LISPだって実装できる - Qiita

 ちょっとドン引きである。

 ここまでやられるとこの記事で書くべきこと(リスト内包表記の悪用的なネタ)はあまり残っていないので、今後はちびちび「うまく書けなくて気持ち悪いよね」的なネタを書いていこうと思う。

更新履歴

  • 2018/2/8 記事作成。とりあえず「二回評価されるのを避けたかった」「行列Tを固有値分解」「forのよさを殺していくスタイル」の3項目追加
  • 2018/2/12 「リスト内包表記でファイル出力」を追加
  • 2018/3/30 「forのよさを殺していくスタイル」に追記。「無限ループ」「リスト内包表記で走査しながらremove」追加
  • 2018/4/21 「チューリング完全」追加(リンクのみ)

*1:二個目のforで動的にリストオブジェクトを生成するオーバーヘッドが重いはず