明後日の場所でUnicodeDecodeError
Google App Engine の webapp フレームワークを使ってコードを書く。
↓これは大丈夫だが、
class Page1(webapp.RequestHandler): def get(self): self.response.headers['Content-Type'] = 'text/plain' self.response.out.write('abc') self.response.out.write(u'あいう')
↓これはだめ。
class Page2(webapp.RequestHandler): def get(self): self.response.headers['Content-Type'] = 'text/plain' self.response.out.write(u'abc') self.response.out.write('あいう') # GAE/Python 1.4.1
ファイルオブジェクトとはまったく逆の挙動だな。
traceback はこんな感じ。
<type 'exceptions.UnicodeDecodeError'>: 'ascii' codec can't decode byte 0x82 in position 0: ordinal not in range(128) Traceback (most recent call last): File "/base/data/home/apps/<app name>/<version number>/<file name>", line 27, in main util.run_wsgi_app(application) File "/base/python_runtime/python_lib/versions/1/google/appengine/ext/webapp/util.py", line 97, in run_wsgi_app run_bare_wsgi_app(add_wsgi_middleware(application)) File "/base/python_runtime/python_lib/versions/1/google/appengine/ext/webapp/util.py", line 115, in run_bare_wsgi_app result = application(env, _start_response) File "/base/python_runtime/python_lib/versions/1/google/appengine/ext/webapp/__init__.py", line 535, in __call__ response.wsgi_write(start_response) File "/base/python_runtime/python_lib/versions/1/google/appengine/ext/webapp/__init__.py", line 248, in wsgi_write body = self.out.getvalue() File "/base/python_runtime/python_dist/lib/python2.5/StringIO.py", line 270, in getvalue self.buf += ''.join(self.buflist)
例外は自分が書いたコード以外の場所で発生しているようだ。
調べてみると StringIO が原因だった。 self.response.out には StringIO のインスタンスが入っている。ソースを読むと StringIO は基本的に誰でもウェルカムで、 str でも unicode でも辞書でも数字でも何でも書き込めることがわかる。
from StringIO import StringIO s = StringIO() s.write('abc') s.write(u'def') s.write({'a' : 1}) s.write(123) print repr(s.getvalue()) # u"abcdef{'a': 1}123"
basestring でないものが入力された場合は黙って str() にかけてからバッファに積む。 getvalue() や read() されたらバッファを連結して返す、というシンプルな動作。
これで問題になるのは、ASCII 以外の文字を含む str と、unicode を書き込んだ時である。 StringIO はこの2つが入力されても特に気にすることなく連結しているので、当然 UnicodeDecodeError が発生する*1。ややこしいことに、実際の連結作業が行われるのは read() や getvalue() した時で、 write() した時にはエラーにならない。
from StringIO import StringIO s = StringIO() s.write(u'abc') s.write('あいう') # # 何か別の処理 ... # body = s.getvalue() # ここでエラー
これは単純に str と unicode を混ぜないことで防止できるわけだが、 Google App Engine が提供する機能の中には、どちらの文字列を返すのかドキュメントにはっきりと書かれていない部分もあるので注意した方がいい。例えばフォームの値を取得しようとすると意外にも unicode が返ってくる。
class FrontDesk(webapp.RequestHandler): def post(self): self.response.headers['Content-Type'] = 'text/plain' self.response.out.write(repr(type(self.request.get('value1')))) # <type 'unicode'>
ちなみに self.response.out の話に戻るけど、 unicode も最終的にブラウザへ送るときは適当な文字コードで符号化されるはずである。そちらはどうなっているかというと、
if isinstance(body, unicode): body = body.encode('utf-8')
というコードが webapp フレームワークに埋め込まれているので大丈夫。フォームとは違って、なぜか任意の文字コードを指定できるようになってないんだな。
参考
*1: UnicodeError が発生するかもしれないと StringIO のドキュメントに注意書きがある。