和英混在文書の表記ゆれ統一のために unicodedata.normalize

zenhan.pyと Python 標準の unicodedata.normalize を組み合わせてこんなことをしていた:

1 # -*- coding: utf-8 -*-
2 import unicodedata
3 import zenhan
4 # ...(省略)...
5 value = unicodedata.normalize('NFKC', zenhan.z2h(unicode(value.strip())))

zenhan.pyは今や PyPI に登録されているのであまり気にしなくても良くなったが、以前はこれを探し当てるのに苦労していた記憶がある。ので、いつも zenhan.py を抱え込んで使っていた。

そんなこともあって、unicodedata.normalize だけで何が気に入らなかったのか、Python 標準だけでやる方法がないか、改めて整理しておこうと思った。

どうやらこれらしい:

1 # -*- coding: utf-8 -*-
2 import unicodedata
3 import codecs
4 import sys
5 sys.stdout = codecs.getwriter('utf-8')(sys.stdout)
6 
7 for s in u"!”#$%&’()-=^~¥|@‘{;+:*}<>?/":
8     n = unicodedata.normalize('NFKC', s)
9     print(u'%s\t->\t%s\t%r' % (s, n, n))

「unicodedata.normalizeなんてことをしたいのは」、「unicodeの正規化をしたいのだ」とは限らない。誰もが「unicode技術者」なわけではないし、「さぁ unicode を扱おう」たくてそうしたいのではない。そうではなくて、例えば表記ゆれの統一をしたいわけだ。「いわゆる俗に言う半角カナ」が突如混じるのは読みづらいしテキスト処理プログラムが混乱するから、全角に統一したいし、逆に全角英数は半角英数に直したい。

そういう用途では

1 unicodedata.normalize('NFKC', s)

は、上に上げた一部除いて満足出来る。特別扱いしたいのは以下3つだけなわけだな:

1 ”   ->   ”   u'\u201d'
2 ’   ->   ’   u'\u2019'
3 ‘   ->   ‘   u'\u2018'

こやつらも ASCII だ、としたいわけだ。(ブログなどからコードをコピペしてきてハメられた記憶がある人には馴染みかもな。)

ので、標準 Python だけでやるなら、こんなかなぁ:

 1 # -*- coding: utf-8 -*-
 2 import unicodedata
 3 import codecs
 4 import sys
 5 import re
 6 sys.stdout = codecs.getwriter('utf-8')(sys.stdout)
 7 
 8 def _repl(m):
 9     s = m.group(1)
10     return {
11         u'\u201d': '"',
12         u'\u2019': "'",
13         u'\u2018': "`",
14         }.get(s, s)
15 
16 for s in u"!”#$%&’()-=^~¥|@‘{;+:*}<>?/":
17     n = unicodedata.normalize(
18         'NFKC',
19         re.sub(ur"([\u201d|\u2019|\u2018])", _repl, s))
20     print(u'%s\t->\t%s\t%r' % (s, n, n))

より「共通処理」っぽく仕立てる第一歩、なら:

 1 # -*- coding: utf-8 -*-
 2 import unicodedata
 3 import re
 4 
 5 def normalize(src):
 6     def _repl(m):
 7         s = m.group(1)
 8         return {
 9             u'\u201d': '"',
10             u'\u2019': "'",
11             u'\u2018': "`",
12             }.get(s, s)
13     return unicodedata.normalize(
14         'NFKC',
15         re.sub(ur"([\u201d|\u2019|\u2018])", _repl, src))


効率のためには re.sub の正規表現のプリコンパイル、などすればいいだろうけど、まぁ、多少の遅さが実用的に問題ないなら、こんなんで十分でしょ。