スクリプトを無理矢理短くすると読みづらくなることを示すサンプル

今から、映画『さくらん』を観に、梅田に行ってくる予定のbonlifeです。
常山日記で取り上げられてたPythonスクリプトがちょっともったいない感じだったので、書き直しているうちに、よりダメな感じになってしまいました。恥さらしのために公開します。
元ネタはこちら。

で、私が書き直した例が以下の通りです。

import re

r = re.compile(r'\bmt_[-_a-z0-9]+\b', re.I)
s = set()

[[s.add(g) for g in r.findall(line)] for line in file("update_tables.txt")]

file("result.txt","w").writelines(x + '\n' for x in sorted(s))

まず、正規表現の部分を見直し。

r=re.compile('[Mm][Tt]\_[\_a-zA-Z0-9]+')

大文字小文字を区別せずにマッチさせたいのであれば、reモジュールを有効活用しましょう。ということで、re.complieの2つ目の引数として re.IGNORECASE の省略形である re.I を指定。この方が正規表現の中身が見やすくなるんじゃないかしら。後、細かいエスケープ処理を意識しないで済むように、正規表現の前に r を指定してraw文字列として扱うと便利。(そもそもアンダーバーはエスケープ不要のはず。)これは余分かもしれませんが、単語の区切りとして \b を書いておいた方が精度が高くなるんじゃないかしら。で、結果は以下の通り。(あんまり見やすさが変わってないのは気のせい!?)

r = re.compile(r'\bmt_[-_a-z0-9]+\b', re.I)

続いて、ファイルを一行ずつ読み込んで処理する部分。

for line in rf.readlines():
  for g in r.findall(line):
    s.add(g)

実際の動作はよく分かりませんが、readlines()って一度ファイルを全部読み込んでしまったはず。fileオブジェクトはイテレート可能なオブジェクトで、イテレートした場合には一行ずつ返してくれます。(イテレートって言葉の使い方が間違ってたらスミマセン!) なので、以下のようにした方がベターっぽい。

for line in rf:
  for g in r.findall(line):
    s.add(g)

後、ファイルを一度読み込むだけだったら、開いたり閉じたりの手間が面倒なので、そのままfileオブジェクトを渡してみる。

for line in file("update_tables.txt"):
  for g in r.findall(line):
    s.add(g)

これでcloseする処理が不要になりました。(このあたりからやり過ぎ感が漂ってきているような。) 勢いでPythonのカッコ良さの代表格であるリスト内包表現(のネスト)を使って書き直してみる。

[[s.add(g) for g in r.findall(line)] for line in file("update_tables.txt")]

あぁ、なんかもう完全にアレですね…(苦笑)。最後に取得した内容を書き込む処理。

s=sorted(s) 
wf.writelines(x + '\n' for x in s) 

このsetオブジェクトをsortedした結果を再度同じ変数に代入してるところが気持ち悪いのは私だけでしょうか。この瞬間setオブジェクトがlistオブジェクトに変わっちゃってるわけで。もう、sorted(s) をそのまま渡しちゃえば良いでしょう、ということで、以下のように書き直し。

file("result.txt","w").writelines(x + '\n' for x in sorted(s))

やってみて気付いたのは、ファイル名が直にスクリプトの中ほどに入っていると見通しが悪いってこと。fileオブジェクトを閉じ忘れてもだいたい問題ないし、最初に生成してしまっても問題ないですね。あるいは、ファイル名だけを infile とか outfile って名前の変数に入れておいて、fileオブジェクトは必要になった時に生成するとか…って考えるとキリがないので、このあたりにしておきます。
色々書きましたが、言いたかったのは以下の2点です。

  • 正規表現のオプションを有効活用しよう!
  • fileオブジェクトをもうちょい優しく扱ってあげよう!

(Python素人が偉そうなこと言ってスミマセン。)