Pythonで以下のような破壊的ループをしようとすると、indexのズレが発生してすべての要素に対して処理が行われないケースがある。
(この例の処理ではリスト内包表記で充分代替可能だったりするが、あくまで例として単一処理にしている。)
li = [ { "id": "D028xxxxx", "is_im": True, "user": "USLACKBOT", "created": 1397471294, "is_user_deleted": False }, { "id": "D028xxxxx", "is_im": True, "user": "U028xxxxx", "created": 1397471294, "is_user_deleted": False }, { "id": "D028xxxxx", "is_im": False, "user": "U028xxxxx", "created": 1397471294, "is_user_deleted": False } ] for user in li: if user['is_im'] is True: li.remove(user) print(li) # [{'id': 'D028QH1PT', 'created': 1397471294, 'user': 'U028NTG5T', 'is_im': True, 'is_user_deleted': False}, {'id': 'D028QH1PR', 'created': 1397471294, 'user': 'U028P546G', 'is_im': False, 'is_user_deleted': False}] # 'is_im': Trueの要素が残っている
リスト全体のスライスコピーを取る
正しく破壊的ループをする場合には、リスト全体をコピーして実行すると良い。
ループ処理前に temp_li = li
を行うやり方もWebでは散見されたが、
li[:]
によるスライスコピーが一番綺麗に記述できる。
for user in li[:]: if user['is_im'] is True: li.remove(user) print(li) # [{'created': 1397471294, 'id': 'D028QH1PR', 'is_im': False, 'is_user_deleted': False, 'user': 'U028P546G'}]
filter関数を使う
ちなみにfilter()で書くとこう。
li = list(filter(lambda user: user['is_im'] is False, li)) print(li) # [{'created': 1397471294, 'id': 'D028QH1PR', 'is_im': False, 'is_user_deleted': False, 'user': 'U028P546G'}]
Python3からはfilter()はリストでなくイテレータを返却するようになったので、list()で囲う必要がある。
リスト内包表記を使う
結局はリスト内包表記がいいのかも知れない。
li = [user for user in li if user['is_im'] is False] print(li) # [{'created': 1397471294, 'id': 'D028QH1PR', 'is_im': False, 'is_user_deleted': False, 'user': 'U028P546G'}]
上記の内容はPythonチュートリアルに記載されていた。数年ぶりに読み返したが、まだ発見があったのでまだまだだなと痛感。

- 作者: Guido van Rossum,鴨澤眞夫
- 出版社/メーカー: オライリージャパン
- 発売日: 2010/02/22
- メディア: 単行本(ソフトカバー)
- 購入: 4人 クリック: 136回
- この商品を含むブログ (23件) を見る
一応速度も計測してみたが、今回のリストだと速度に顕著な差異が見られないので割愛した。 コードはPython3.xに準拠。