Pythonで正しく日本語を eval する
@atsuoishimoto s = eval(compile(u"""'あいう'""")) すると、 s はWindowsでもUTF-8でエンコードされた文字列になっちゃうんですよ。
これはしたり!ちっとも気がつかなかった!普通のアプリ書いてるとcompile()やeval()を使うことはあんまりないからなぁ。@methaneさんが バグ報告 もしているが、Python2系列は知らね、とばかりにcloseされてしまっている。
>>> print eval("'あいうえお'") あいうえお >>> print repr(eval("u'あいうえお'")) u'\x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8' >>> print repr(eval(u"'あいうえお'")) '\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a' >>> print eval(u"u'あいうえお'") あいうえお
うーん。eval()にASCII文字列を渡すとUnicode文字列がASCII文字列のまま、デコードされずに構築されてしまうし、Unicode文字列の場合はソース中のASCII文字列がUTF-8に変換されてしまうようだ。
とりあえず日本語を使ったevalをなんとかしないといけない。先ほどのBug報告ではソースにエンコードを指定するコメントを追加する方法が提案されているが、ソース行が一行増えてしまうとエラー表示がずれたりするし、なんとなくハックっぽい感じもする。
こういう時は astモジュール を使うのが良いだろう。astモジュールを使えばPythonのソースを抽象構文木に変換し、コードを動的に変換できるのである。抽象構文木とか言うとなんか難しそうだが、平たく言えばソースコードの構成要素を、操作しやすいツリー状のデータに変換したものだ。
ここでは、ソースをUnicodeに変換してから抽象構文木を構築し、ASCIIリテラルをUTF-8から指定したエンコードに変換すれば良いだろう。
import ast class _Transform(ast.NodeTransformer): def __init__(self, encode): super(_Transform, self).__init__() self._encode = encode def visit_Str(self, node): if not isinstance(node.s, unicode): s = unicode(node.s, 'utf-8') node.s = s.encode(self._encode) return node def ueval(src, encode, filename): src = unicode(src, encode) expr = compile(src, filename, "eval", ast.PyCF_ONLY_AST) _Transform(encode).visit(expr.body) return eval(compile(expr, filename, "eval"))
使い方はこんな感じになる
>>> print ueval("'あいうえお'+'かきくけこ'", "cp932", "filename") あいうえおかきくけこ >>> print ueval("u'あいうえお'+u'かきくけこ'", "cp932", "filename") あいうえおかきくけこ >>