兎の真似をする烏

全力で"楽"である為に・人生が"面白く"ある為に

半角・全角の相互変換について

やりたいこと

「全角から半角」への変換
「半角から全角」への変換
それぞれの相互変換がやりたい。

当初はjaconvで実施しようとしたのだけれど、Anacondaでパッケージの追加をしようとしたけど存在しなかった(´・ω・`)
リポジトリを追加してみたけど、バージョン周りで引っかかってしまったので、自分で作ることにした。
(執筆当時はPython3.7を使用中)

変換は以下の4パターンに切り分けて実施できるようにする。

  • 記号
  • 数字
  • 英字
  • カタカナ

「記号」変換

全角 → 半角

# 半角記号
HALF_SYMBOL = ''.join(chr(0x21 + i) for i in range(15)) # !"#$%&'()*+,-./
HALF_SYMBOL += ''.join(chr(0x3a + i) for i in range(7)) # :;<=>?@
HALF_SYMBOL += ''.join(chr(0x5b + i) for i in range(6)) # [\]^_`
HALF_SYMBOL += ''.join(chr(0x7b + i) for i in range(4)) # {|}~
HALF_SYMBOL += chr(0xa5)                                # ¥

# 全角記号
DOUBLE_SYMBOL = ''.join(chr(0xff01 + i) for i in range(15)) # !"#$%&'()*+,-./
DOUBLE_SYMBOL += ''.join(chr(0xff1a + i) for i in range(7)) # :;<=>?@
DOUBLE_SYMBOL += ''.join(chr(0xff3b + i) for i in range(6)) # [\]^_`
DOUBLE_SYMBOL += ''.join(chr(0xff5b + i) for i in range(4)) # {|}~
DOUBLE_SYMBOL += chr(0xffe5)                                # ¥

def transfer_symbol_double_to_half(text):
    double_to_half_symbol = str.maketrans(DOUBLE_SYMBOL, HALF_SYMBOL)
    return text.translate(double_to_half_symbol)

if __name__ == '__main__':
    text = '!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~¥'
    print(transfer_symbol_double_to_half(text))

結果

!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~¥

半角 → 全角

# 半角記号
HALF_SYMBOL = ''.join(chr(0x21 + i) for i in range(15)) # !"#$%&'()*+,-./
HALF_SYMBOL += ''.join(chr(0x3a + i) for i in range(7)) # :;<=>?@
HALF_SYMBOL += ''.join(chr(0x5b + i) for i in range(6)) # [\]^_`
HALF_SYMBOL += ''.join(chr(0x7b + i) for i in range(4)) # {|}~
HALF_SYMBOL += chr(0xa5)                                # ¥

# 全角記号
DOUBLE_SYMBOL = ''.join(chr(0xff01 + i) for i in range(15)) # !"#$%&'()*+,-./
DOUBLE_SYMBOL += ''.join(chr(0xff1a + i) for i in range(7)) # :;<=>?@
DOUBLE_SYMBOL += ''.join(chr(0xff3b + i) for i in range(6)) # [\]^_`
DOUBLE_SYMBOL += ''.join(chr(0xff5b + i) for i in range(4)) # {|}~
DOUBLE_SYMBOL += chr(0xffe5)                                # ¥

def transfer_symbol_half_to_double(text):
    half_to_double_symbol = str.maketrans(HALF_SYMBOL, DOUBLE_SYMBOL)
    return text.translate(half_to_double_symbol)


if __name__ == '__main__':
    text = '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~¥'
    print(transfer_symbol_half_to_double(text))

結果

!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~¥

「数字」変換

全角 → 半角

# 半角数値
HALF_NUMBER = ''.join(chr(0x30 + i) for i in range(10)) # 0123456789

# 全角数値
DOUBLE_NUMBER = ''.join(chr(0xff10 + i) for i in range(10)) # 0123456789

def transfer_number_double_to_half(text):
    double_to_half_number = str.maketrans(DOUBLE_NUMBER, HALF_NUMBER)
    return text.translate(double_to_half_number)

if __name__ == '__main__':
    text = '0123456789'
    print(transfer_number_double_to_half(text))

結果

0123456789

半角 → 全角

# 半角数値
HALF_NUMBER = ''.join(chr(0x30 + i) for i in range(10)) # 0123456789

# 全角数値
DOUBLE_NUMBER = ''.join(chr(0xff10 + i) for i in range(10)) # 0123456789

def transfer_number_half_to_double(text):
    half_to_double_number = str.maketrans(HALF_NUMBER, DOUBLE_NUMBER)
    return text.translate(half_to_double_number)

if __name__ == '__main__':
    text = '0123456789'
    print(transfer_number_half_to_double(text))

結果

0123456789

「英字」変換

全角 → 半角

# 半角英字
HALF_ALPHABET = ''.join(chr(0x41 + i) for i in range(26))  # ABCDEFGHIJKLMNOPQRSTUVWXYZ
HALF_ALPHABET += ''.join(chr(0x61 + i) for i in range(26)) # abcdefghijklmnopqrstuvwxyz

# 全角英字
DOUBLE_ALPHABET = ''.join(chr(0xff21 + i) for i in range(26))  # ABCDEFGHIJKLMNOPQRSTUVWXYZ
DOUBLE_ALPHABET += ''.join(chr(0xff41 + i) for i in range(26)) # abcdefghijklmnopqrstuvwxyz

def transfer_alphabet_double_to_half(text):
    double_to_half_alphabet = str.maketrans(DOUBLE_ALPHABET, HALF_ALPHABET)
    return text.translate(double_to_half_alphabet)

if __name__ == '__main__':
    text = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    text += 'abcdefghijklmnopqrstuvwxyz'
    print(transfer_alphabet_double_to_half(text))

結果

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz

半角 → 全角

# 半角英字
HALF_ALPHABET = ''.join(chr(0x41 + i) for i in range(26))  # ABCDEFGHIJKLMNOPQRSTUVWXYZ
HALF_ALPHABET += ''.join(chr(0x61 + i) for i in range(26)) # abcdefghijklmnopqrstuvwxyz

# 全角英字
DOUBLE_ALPHABET = ''.join(chr(0xff21 + i) for i in range(26))  # ABCDEFGHIJKLMNOPQRSTUVWXYZ
DOUBLE_ALPHABET += ''.join(chr(0xff41 + i) for i in range(26)) # abcdefghijklmnopqrstuvwxyz

def transfer_alphabet_half_to_double(text):
    half_to_double_alphabet = str.maketrans(HALF_ALPHABET, DOUBLE_ALPHABET)
    return text.translate(half_to_double_alphabet)

if __name__ == '__main__':
    text = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    text += 'abcdefghijklmnopqrstuvwxyz'
    print(transfer_alphabet_half_to_double(text))

結果

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz

「カタカナ」変換

全角 → 半角

# 半角カナ
HALF_KANA = chr(0xff6f) + chr(0xff65) + chr(0xff70)                              # ツ・ー
HALF_KANA += ''.join(chr(0xff6c + i) for i in range(3))                          # ャュョ
HALF_KANA += ''.join(chr(0xff67 + i) for i in range(5))                          # ァィゥェォ
HALF_KANA += chr(0xff9c) + chr(0xff72) + chr(0xff74) + chr(0xff66) + chr(0xff9d) # ワイエヲン
HALF_KANA += ''.join(chr(0xff97 + i) for i in range(5))                          # ラリルレロ
HALF_KANA += ''.join(chr(0xff94 + i) for i in range(3))                          # ヤユヨ
HALF_KANA += ''.join(chr(0xff8f + i) for i in range(5))                          # マミムメモ
HALF_KANA += ''.join(chr(0xff8a + i) for i in range(5))                          # ハヒフヘホ
HALF_KANA += ''.join(chr(0xff85 + i) for i in range(5))                          # ナニヌネノ
HALF_KANA += ''.join(chr(0xff80 + i) for i in range(5))                          # タチツテト
HALF_KANA += ''.join(chr(0xff7b + i) for i in range(5))                          # サシスセソ
HALF_KANA += ''.join(chr(0xff76 + i) for i in range(5))                          # カキクケコ
HALF_KANA += ''.join(chr(0xff71 + i) for i in range(5))                          # アイウエオ

# 半角カナ-濁音
HALF_KANA_VOICED = ''.join(chr(0xff76 + i) + chr(0xff9e) for i in range(5))                                                               # ガギグゲゴ
HALF_KANA_VOICED += ''.join(chr(0xff7b + i) + chr(0xff9e) for i in range(5))                                                              # ザジズゼゾ
HALF_KANA_VOICED += ''.join(chr(0xff80 + i) + chr(0xff9e) for i in range(5))                                                              # ダヂヅデド
HALF_KANA_VOICED += ''.join(chr(0xff8a + i) + chr(0xff9e) for i in range(5))                                                              # バビブベボ
HALF_KANA_VOICED += (chr(0xff73) + chr(0xff9e))                                                                                           # ヴ
HALF_KANA_VOICED += (chr(0xff9c) + chr(0xff9e)) + (chr(0xff72) + chr(0xff9e)) + (chr(0xff74) + chr(0xff9e)) + (chr(0xff66) + chr(0xff9e)) # ヷイ゙エ゙ヺ
HALF_KANA_VOICED += ''.join(chr(0xff8a + i) + chr(0xff9f) for i in range(5))                                                              # パピプペポ

# 全角カナ
DOUBLE_KANA = chr(0x30c3) + chr(0x30fb) + chr(0x30fc)                                                              # ッ・ー
DOUBLE_KANA += ''.join(chr(0x30e3 + (i * 2)) for i in range(3))                                                    # ャュョ
DOUBLE_KANA += ''.join(chr(0x30a1 + (i * 2)) for i in range(5))                                                    # ァィゥェォ
DOUBLE_KANA += ''.join(chr(0x30ef + i) for i in range(5))                                                          # ワヰヱヲン
DOUBLE_KANA += ''.join(chr(0x30e9 + i) for i in range(5))                                                          # ラリルレロ
DOUBLE_KANA += ''.join(chr(0x30e4 + (i * 2)) for i in range(3))                                                    # ヤユヨ
DOUBLE_KANA += ''.join(chr(0x30de + i) for i in range(5))                                                          # マミムメモ
DOUBLE_KANA += ''.join(chr(0x30cf + (i * 3)) for i in range(5))                                                    # ハヒフヘホ
DOUBLE_KANA += ''.join(chr(0x30ca + i) for i in range(5))                                                          # ナニヌネノ
DOUBLE_KANA += ''.join(chr(0x30bf + (i * 2)) for i in range(2)) + ''.join(chr(0x30c4 + (i * 2)) for i in range(3)) # タチツテト
DOUBLE_KANA += ''.join(chr(0x30b5 + (i * 2)) for i in range(5))                                                    # サシスセソ
DOUBLE_KANA += ''.join(chr(0x30ab + (i * 2)) for i in range(5))                                                    # カキクケコ
DOUBLE_KANA += ''.join(chr(0x30a2 + (i * 2)) for i in range(5))                                                    # アイウエオ

# 全角カナ-濁音
DOUBLE_KANA_VOICED = ''.join(chr(0x30ac + (i * 2)) for i in range(5))                                                     # ガギグゲゴ
DOUBLE_KANA_VOICED += ''.join(chr(0x30b6 + (i * 2)) for i in range(5))                                                    # ザジズゼゾ
DOUBLE_KANA_VOICED += ''.join(chr(0x30c0 + (i * 2)) for i in range(2)) + ''.join(chr(0x30c5 + (i * 2)) for i in range(3)) # ダヂヅデド
DOUBLE_KANA_VOICED += ''.join(chr(0x30d0 + (i * 3)) for i in range(5))                                                    # バビブベボ
DOUBLE_KANA_VOICED += chr(0x30f4)                                                                                         # ヴ
DOUBLE_KANA_VOICED += ''.join(chr(0x30f7 + i) for i in range (4))                                                         # ヷヸヹヺ
DOUBLE_KANA_VOICED += ''.join(chr(0x30d1 + (i * 3)) for i in range(5))                                                    # パピプペポ

def transfer_kana_double_to_half(text):
    double_to_half_kana = str.maketrans(DOUBLE_KANA, HALF_KANA)
    double_to_half_kana_voiced = {}
    for i in range(len(DOUBLE_KANA_VOICED)):
        double_to_half_kana_voiced.update(str.maketrans({DOUBLE_KANA_VOICED[i]: HALF_KANA_VOICED[i*2:i*2+2]}))
    text = text.translate(double_to_half_kana)
    text = text.translate(double_to_half_kana_voiced)
    return text

if __name__ == '__main__':
    text = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヰヱヲン'
    text += 'ァィゥェォャュョッ'
    text += '・ー'
    text += 'ガギグゲゴザジズゼゾダヂヅデドバビブベボヴヷヸヹヺパピプペポ'
    print(transfer_kana_double_to_half(text))

結果

アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワイエヲンァィゥェォャュョッ・ーガギグゲゴザジズゼゾダヂヅデドバビブベボヴヷイ゙エ゙ヺパピプペポ

全角 → 半角

import re

# 半角カナ
HALF_KANA = chr(0xff6f) + chr(0xff65) + chr(0xff70)                              # ツ・ー
HALF_KANA += ''.join(chr(0xff6c + i) for i in range(3))                          # ャュョ
HALF_KANA += ''.join(chr(0xff67 + i) for i in range(5))                          # ァィゥェォ
HALF_KANA += chr(0xff9c) + chr(0xff72) + chr(0xff74) + chr(0xff66) + chr(0xff9d) # ワイエヲン
HALF_KANA += ''.join(chr(0xff97 + i) for i in range(5))                          # ラリルレロ
HALF_KANA += ''.join(chr(0xff94 + i) for i in range(3))                          # ヤユヨ
HALF_KANA += ''.join(chr(0xff8f + i) for i in range(5))                          # マミムメモ
HALF_KANA += ''.join(chr(0xff8a + i) for i in range(5))                          # ハヒフヘホ
HALF_KANA += ''.join(chr(0xff85 + i) for i in range(5))                          # ナニヌネノ
HALF_KANA += ''.join(chr(0xff80 + i) for i in range(5))                          # タチツテト
HALF_KANA += ''.join(chr(0xff7b + i) for i in range(5))                          # サシスセソ
HALF_KANA += ''.join(chr(0xff76 + i) for i in range(5))                          # カキクケコ
HALF_KANA += ''.join(chr(0xff71 + i) for i in range(5))                          # アイウエオ

# 半角カナ-濁音
HALF_KANA_VOICED = ''.join(chr(0xff76 + i) + chr(0xff9e) for i in range(5))                                                               # ガギグゲゴ
HALF_KANA_VOICED += ''.join(chr(0xff7b + i) + chr(0xff9e) for i in range(5))                                                              # ザジズゼゾ
HALF_KANA_VOICED += ''.join(chr(0xff80 + i) + chr(0xff9e) for i in range(5))                                                              # ダヂヅデド
HALF_KANA_VOICED += ''.join(chr(0xff8a + i) + chr(0xff9e) for i in range(5))                                                              # バビブベボ
HALF_KANA_VOICED += (chr(0xff73) + chr(0xff9e))                                                                                           # ヴ
HALF_KANA_VOICED += (chr(0xff9c) + chr(0xff9e)) + (chr(0xff72) + chr(0xff9e)) + (chr(0xff74) + chr(0xff9e)) + (chr(0xff66) + chr(0xff9e)) # ヷイ゙エ゙ヺ
HALF_KANA_VOICED += ''.join(chr(0xff8a + i) + chr(0xff9f) for i in range(5))                                                              # パピプペポ

# 全角カナ
DOUBLE_KANA = chr(0x30c3) + chr(0x30fb) + chr(0x30fc)                                                              # ッ・ー
DOUBLE_KANA += ''.join(chr(0x30e3 + (i * 2)) for i in range(3))                                                    # ャュョ
DOUBLE_KANA += ''.join(chr(0x30a1 + (i * 2)) for i in range(5))                                                    # ァィゥェォ
DOUBLE_KANA += ''.join(chr(0x30ef + i) for i in range(5))                                                          # ワヰヱヲン
DOUBLE_KANA += ''.join(chr(0x30e9 + i) for i in range(5))                                                          # ラリルレロ
DOUBLE_KANA += ''.join(chr(0x30e4 + (i * 2)) for i in range(3))                                                    # ヤユヨ
DOUBLE_KANA += ''.join(chr(0x30de + i) for i in range(5))                                                          # マミムメモ
DOUBLE_KANA += ''.join(chr(0x30cf + (i * 3)) for i in range(5))                                                    # ハヒフヘホ
DOUBLE_KANA += ''.join(chr(0x30ca + i) for i in range(5))                                                          # ナニヌネノ
DOUBLE_KANA += ''.join(chr(0x30bf + (i * 2)) for i in range(2)) + ''.join(chr(0x30c4 + (i * 2)) for i in range(3)) # タチツテト
DOUBLE_KANA += ''.join(chr(0x30b5 + (i * 2)) for i in range(5))                                                    # サシスセソ
DOUBLE_KANA += ''.join(chr(0x30ab + (i * 2)) for i in range(5))                                                    # カキクケコ
DOUBLE_KANA += ''.join(chr(0x30a2 + (i * 2)) for i in range(5))                                                    # アイウエオ

# 全角カナ-濁音
DOUBLE_KANA_VOICED = ''.join(chr(0x30ac + (i * 2)) for i in range(5))                                                     # ガギグゲゴ
DOUBLE_KANA_VOICED += ''.join(chr(0x30b6 + (i * 2)) for i in range(5))                                                    # ザジズゼゾ
DOUBLE_KANA_VOICED += ''.join(chr(0x30c0 + (i * 2)) for i in range(2)) + ''.join(chr(0x30c5 + (i * 2)) for i in range(3)) # ダヂヅデド
DOUBLE_KANA_VOICED += ''.join(chr(0x30d0 + (i * 3)) for i in range(5))                                                    # バビブベボ
DOUBLE_KANA_VOICED += chr(0x30f4)                                                                                         # ヴ
DOUBLE_KANA_VOICED += ''.join(chr(0x30f7 + i) for i in range (4))                                                         # ヷヸヹヺ
DOUBLE_KANA_VOICED += ''.join(chr(0x30d1 + (i * 3)) for i in range(5))                                                    # パピプペポ

def transfer_kana_half_to_double(text):
    half_to_double_kana = str.maketrans(HALF_KANA, DOUBLE_KANA)
    half_to_double_kana_voiced = {}
    for i in range(len(DOUBLE_KANA_VOICED)):
        half_to_double_kana_voiced.update({HALF_KANA_VOICED[i*2:i*2+2]: DOUBLE_KANA_VOICED[i]})
    text = re.sub('({})'.format('|'.join(map(re.escape, half_to_double_kana_voiced.keys()))),
                  lambda key: half_to_double_kana_voiced[key.group()],
                  text)
    text = text.translate(half_to_double_kana)
    return text

if __name__ == '__main__':
    text = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワイエヲン'
    text += 'ァィゥェォャュョッ・ー'
    text += 'ガギグゲゴザジズゼゾダヂヅデドバビブベボヴヷイ゙エ゙ヺパピプペポ'
    print(transfer_kana_half_to_double(text))

結果

アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワイエヲンァィゥェォャュョッ・ーガギグゲゴザジズゼゾダヂヅデドバビブベボヴヷヸヹヺパピプペポ

注意点

定義順序について

半角カナの定義が「ツ・ー」から始まり「アイウエオ」で終了しており、全角カナも「ッ・ー」で始まり「アイウエオ」で終了している。
これには理由がある。

半角から全角へ変換する時、もしも「アイウエオ(アイウエオ)」が先に定義され、「ワヰヱヲン(ワイエヲン)」が後に定義されているとする。
この場合、「イ」は「ヰ」と変換され、「エ」は「ヱ」と変換されてしまう。
どうやらstr.translate()は後発優先で変更されるよう。

「イ」は「イ」、「エ」は「エ」と変換して欲しいので半角カナ・全角カナの定義が順序通りに並んでいない。

半角→全角の変換順番について

半角から全角に変換する順序は濁音を優先する必要がある。

例えば、濁音を後回しにした場合に「ガ」を変換すると「カ」が先に変換され「カ」となり「゙」が取り残される。
「ガ」は「ガ」にしたいので濁音を優先して変換するように調整している。

半角→全角変換における濁音処理について

半角の濁音は2文字で一つの意味を持つことになる。
例えば「ガ」は「カ」と「゙」の二文字から構成されるようになる。
これを思い通りの内容に変換させるにはstr.translate()では実現できない。理由はstr.translate()変換対象が1文字の場合は正常に変換処理が可能であるが、2文字以上の場合はエラーが発生する。
このことから「ガ」を「ガ」に変換したい場合は変換対象が2文字となるため変換できない。

これを解決するために正規表現の置換処理を利用する。

具体的には

re.sub('({})'.format('|'.join(map(re.escape, half_to_double_kana_voiced.keys()))),
       lambda key: half_to_double_kana_voiced[key.group()],
       text)

この部分で置換処理を実施している。