Pythonで全角・半角を変換(mojimojiなど)

Modified: | Tags: Python, 文字列, Unicode

Pythonで全角・半角を変換するには、unicodedata.normalize()やサードパーティライブラリのmojimojiなどを使う方法がある。

半角1文字・全角2文字として文字数をカウントする方法や、文字列から漢字・ひらがな・カタカナ・英数字を抽出してカウントする方法については以下の記事を参照。

Unicode正規化: unicodedata.normalize()

unicodedata.normalize()はUnicode正規化を行う関数。

第一引数に正規化形式を表す文字列'NFC', 'NFKC', 'NFD', 'NFKD'のいずれか、第二引数に処理する文字列を指定する。

全角と半角を変換するといった目的の場合、正規化形式は'NFKC'を指定すればよい。詳細は以下の記事を参照。

unicodedataモジュールをインポートして使う。標準ライブラリに含まれているので追加のインストールは不要。ここでは、正規化形式がNFKCのUnicode正規化によってどのような変換が行われるかを紹介する。

import unicodedata

s = '123abcアイウエオ①㈱㌖'
print(unicodedata.normalize('NFKC', s))
# 123abcアイウエオ1(株)キロメートル

英数字は半角、カタカナは全角に変換される

英数字は半角、カタカナは全角に変換される。

s = '123abcアイウエオ123abcアイウエオ'
print(unicodedata.normalize('NFKC', s))
# 123abcアイウエオ123abcアイウエオ

記号については、ASCII文字は半角に、主に日本語で使われる文字(カギカッコや句読点など)は全角に変換される。

s = '().,「」。、().,「」。、'
print(unicodedata.normalize('NFKC', s))
# ().,「」。、().,「」。、

紛らわしい文字には注意が必要。例えば、(全角チルダ: U+FF5E)は~(半角チルダ: U+007E)に変換されるが、(波ダッシュ: U+301C)は変換されない。

s = '~〜'
print(unicodedata.normalize('NFKC', s))
# ~〜

見分けがつかない文字はord()でUnicodeコードポイントを確認できる。

print([hex(ord(c)) for c in s])
# ['0xff5e', '0x301c']

print([hex(ord(c)) for c in unicodedata.normalize('NFKC', s)])
# ['0x7e', '0x301c']

波ダッシュも半角チルダに変換したい場合は、unicodedata.normalize()のあとで波ダッシュを半角チルダに置換するか、unicodedata.normalize()の前に波ダッシュを全角チルダに変換すればよい。

s = '~〜'
print(unicodedata.normalize('NFKC', s).replace('〜', '~'))
# ~~

print(unicodedata.normalize('NFKC', s.replace('〜', '~')))
# ~~

置換したい文字が複数ある場合は後述のtranslate()が便利。

全角・半角以外も変換される

Unicode正規化では、全角・半角だけでなく、合成済み文字も変換される。

s = '①㈱㌖'
print(unicodedata.normalize('NFKC', s))
# 1(株)キロメートル

変換されそうでも変換されない文字もある。

s = '®©💯'
print(unicodedata.normalize('NFKC', s))
# ®©💯

どのように変換されるかを確実に把握したい場合は、後述のサードパーティライブラリや変換マップを用意する方法を使う。

全角と半角を相互に変換: mojimoji

moijmojiは全角と半角を相互に変換するためのサードパーティライブラリ。pipでインストールする必要がある。

同様の機能を持ったライブラリにはjaconvがある。こちらは全角・半角だけでなく、ひらがな・カタカナの変換も可能。

ここでは、全角・半角の相互変換に特化したmojimojiを紹介する。以降のサンプルコードで使用しているmojimojiはバージョン0.0.12

全角を半角に変換: mojimoji.zen_to_han()

mojimoji.zen_to_han()で全角が半角に変換される。

import mojimoji

s = '123abc!?アイウエオ123abc!?アイウエオ'
print(mojimoji.zen_to_han(s))
# 123abc!?アイウエオ123abc!?アイウエオ

半角を全角に変換: mojimoji.han_to_zen()

mojimoji.han_to_zen()で半角が全角に変換される。

s = '123abc!?アイウエオ123abc!?アイウエオ'
print(mojimoji.han_to_zen(s))
# 123abc!?アイウエオ123abc!?アイウエオ

変換対象を選択: 引数ascii, digit, kana

mojimoji.zen_to_han(), mojimoji.han_to_zen()では引数ascii, digit, kanaで変換対象を選択できる。いずれもデフォルトはTrueで、すべてが変換される。Falseを指定すると変換されない。

s = '123abc!?アイウエオ123abc!?アイウエオ'
print(mojimoji.zen_to_han(s, kana=False))
# 123abc!?アイウエオ123abc!?アイウエオ

print(mojimoji.han_to_zen(s, digit=False, ascii=False))
# 123abc!?アイウエオ123abc!?アイウエオ

それぞれにどのような文字が割り当てられているかはソースコードを参照。kanaには句読点やカギカッコも含まれている。

バージョン0.0.12時点では、バックスラッシュ(\: U+005C, : U+FF3C)が設定されていない。issueを立てておいたのでそのうち対応してくれるかもしれない。

なお、unicodedata.normalize()のように、カナは全角、英数字は半角に変換したい場合は、引数を指定した上でzen_to_han()han_to_zen()を続けて実行する。

print(mojimoji.han_to_zen(mojimoji.zen_to_han(s, kana=False), digit=False, ascii=False))
# 123abc!?アイウエオ123abc!?アイウエオ

変換マップを用意して変換: translate()

自分で変換マップを用意して変換する方法もある。文字列strtranslate()メソッドを使う。変換マップはstr.maketrans()で生成する。

str.maketrans()の第一引数・第二引数に同じ長さの文字列を指定すると、第一引数の文字が第二引数の対応する文字(同じ位置の文字)に置換される。

例えば数字のみ半角・全角を変換したい場合は以下のようにする。

z_digit = '1234567890'
h_digit = '1234567890'

z2h_digit = str.maketrans(z_digit, h_digit)
h2z_digit = str.maketrans(h_digit, z_digit)

s = '123123'
print(s.translate(z2h_digit))
# 123123

print(s.translate(h2z_digit))
# 123123

Unicodeコードポイントが連続している文字の場合は以下のように変換マップを生成できる。

以下は、ASCIIの印字可能文字を変換する例。全角スペース(U+3000)は他の全角のASCII文字とコードポイントが離れているので先頭に追加している。

z_ascii = '\u3000' + ''.join(chr(i) for i in range(0xFF01, 0xFF5E + 1))
print(z_ascii)
#  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~

h_ascii = ''.join(chr(i) for i in range(0x0020, 0x007E + 1))
print(h_ascii)
#  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~

z2h_ascii = str.maketrans(z_ascii, h_ascii)
h2z_ascii = str.maketrans(h_ascii, z_ascii)

s = '123abc!?123abc!?'
print(s.translate(z2h_ascii))
# 123abc!?123abc!?

print(s.translate(h2z_ascii))
# 123abc!?123abc!?

ASCIIには円記号(: U+00A5, ¥: U+FFE5)は含まれない。追加したい場合は、文字列の末尾に+演算子で追加すればよい。その他の文字も同じ要領で追加できる。

z_ascii_yen = z_ascii + '¥'
h_ascii_yen = h_ascii + '¥'

z2h_ascii_yen = str.maketrans(z_ascii_yen, h_ascii_yen)
h2z_ascii_yen = str.maketrans(h_ascii_yen, z_ascii_yen)

s = '123abc!?¥123abc!?¥'
print(s.translate(z2h_ascii_yen))
# 123abc!?¥123abc!?¥

print(s.translate(h2z_ascii_yen))
# 123abc!?¥123abc!?¥

なお、濁音・半濁音の半角カナは2文字として扱われるため、str.maketrans()では変換マップを生成できない。

print(len('ガ'))
# 1

print(list('ガ'))
# ['ガ']

print(len('ガ'))
# 2

print(list('ガ'))
# ['カ', '゙']

# str.maketrans('ガ', 'ガ')
# ValueError: the first two maketrans arguments must have equal length

半角カナの処理はunicodedata.normalize()やmojimojiなどのライブラリを使うほうが楽。

関連カテゴリー

関連記事