Python WSGI で毎回 encode(‘utf-8’) するのが面倒

Python の WSGI プログラムで出力をするたび

yield '<h1>テスト</h1>'.encode('utf-8')
yield '<p>あいうえお</p>'.encode('utf-8')

と encode('utf-8') を付けていましたが、どう考えてもこれは手間だし無駄な気がします。Flask を使えばいいと言われたらお終いなので、何か手はないかと考えてみました。

Python にはデコレータ(decorator) という仕組があって、任意の関数の前後に好きな処理を追加することができます。関数も第一級オブジェクトである Python の特性をうまく利用した素敵機能ですね。

yield を扱った decorator の例があまり見つからなくてやや苦戦しましたが、次のような decorator で実際に動作することを確認しました。

def encode_utf8(func):
	def wrapper(*args, **kwargs):
		for r in func(*args, **kwargs):
			yield r.encode('utf-8')
	return wrapper

前回の WSGI でフォームを扱うプログラムに適用させてみたのが次のコードです。

# coding: utf-8

from urllib.parse import parse_qsl

def encode_utf8(func):
	def wrapper(*args, **kwargs):
		for r in func(*args, **kwargs):
			yield r.encode('utf-8')
	return wrapper

@encode_utf8
def application(env, res):
	res('200 OK', [('Content-type', 'text/html; charset=utf-8')])

	# Form
	yield '<form method="get" accept-charset="UTF-8"><p><input type="text" name="get" value=""><input type="submit" value="GET"></form>'
	yield '<form method="post" accept-charset="UTF-8"><p><input type="text" name="post" value=""><input type="submit" value="POST"></form>'

	# Request method
	method = env.get('REQUEST_METHOD')
	yield '<p>Method: {}</p>'.format(method)

	# Get query string
	if method == 'POST':
		wsgi_input = env['wsgi.input']
		query = wsgi_input.read(int(env.get('CONTENT_LENGTH', 0))).decode('utf-8')
	else:
		query = env.get('QUERY_STRING', '')
	yield '<p>Query: {}</p>'.format(query)

	# Output form fields
	form = parse_qsl(query)
	for k, v in form:
		yield '<p>{} = {}</p>'.format(k, v)

行数は変らないのでぱっと見の変化少ないですが、書き忘れて Internal Server Error をもらう率は少し減りますね。