【Python】一行で全角と半角を相互変換する（英字＋数字＋記号）

テキスト分析などやっていると遭遇する全角／半角の変換処理をライブラリに頼らずにやります。
英字、英数字、記号の変換となります半角カナなどには対応していません。
Python 3.6.7 (Jupyter Notebook経由)で確認しています。

googleで探した限りでは、そうとうシンプルな部類と思います。

**更新.1 指摘を頂き、変換部分を辞書内包表記に後半の全角・半角データ生成部分をジェネレータ内包表記に変更しました(2019/04/19)**
**更新.2 指摘を頂き、str.maketrans()の利用方法の別バージョンの追記と説明の更新を行いました(2019/04/20)**

**※※重要※※**
**テキストの各行を変換するなど、自然言語処理などで大量の変換処理を行う場合は下の一行の例ではなく、記事後半の[変換テーブルを予め作成しておく方法](#変換テーブルを予め作成する例)の方が計算コストの面で良いと思われます。**

少し形を変えてしまいましたが、こちらの記事を参考にさせていただいています。
http://eneprog.blogspot.com/2018/09/pythonunicodedata.html

# 全角 -> 半角
```{Python}
# 元の文字列
text = "！＂＃＄％＆＇（）＊＋，－．／０１２３４５６７８９：；＜＝＞？＠ＡＢＣＤＥＦＧＨＩＪＫＬＭＮＯＰＱＲＳＴＵＶＷＸＹＺ［＼］＾＿｀>？＠ａｂｃｄｅｆｇｈｉｊｋｌｍｎｏｐｑｒｓｔｕｖｗｘｙｚ｛｜｝～"

# 変換
text.translate(str.maketrans({chr(0xFF01 + i): chr(0x21 + i) for i in range(94)}))

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

# 半角 -> 全角
```{Python}
# 元の文字列("と\の記号は「\"」「\\」としてエスケープしています。
text = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"

# 変換
text.translate(str.maketrans({chr(0x0021 + i): chr(0xFF01 + i) for i in range(94)}))

# 結果
# '！＂＃＄％＆＇（）＊＋，－．／０１２３４５６７８９：；＜＝＞？＠ＡＢＣＤＥＦＧＨＩＪＫＬＭＮＯＰＱＲＳＴＵＶＷＸＹＺ［＼］＾＿｀ａｂｃｄｅｆｇｈｉｊｋｌｍｎｏｐｑｒｓｔｕｖｗｘｙｚ｛｜｝～'
```

文字列一括変換を行う関数translateに、変換前の文字をキー、返還後の文字をバリューとした辞書から作成した変換テーブルを渡して変換しています。

# 利用している関数

## translate()

変換テーブルとして与えられた情報を元に、文字の置き換えを実施します
変換テーブルはstr.maketrans()に辞書を与えて作成します
https://docs.python.org/ja/3/library/stdtypes.html#str.translate

```{Python}
text = "abcdefg"

trans_table = str.maketrans({"a":"A", "d":"D"})
text.translate(trans_table)

# 結果
# 'AbcDefg'
```

## str.maketrans()

translate()に与える変換テーブルを作成するための静的関数です
https://docs.python.org/ja/3/library/stdtypes.html#str.maketrans

~~（なぜtranslate()と統合しないのだろう）~~
作成済みの変換テーブルを使いまわせば、毎回生成するコストを削減できます。
特に大量のコーパスに変換をかける際には重要になりそうです。

次のように、辞書の代わりに変換元と変換先の二つの文字列を与えて変換テーブルを作成することもできます。一行にこだわらなければこちらの方が分かりやすいですね。
(shiracamusさん、ありがとうございます！)

####変換テーブルを予め作成する例

```{Python}
ZEN = "".join(chr(0xff01 + i) for i in range(94))
HAN = "".join(chr(0x21 + i) for i in range(94))

ZEN2HAN = str.maketrans(ZEN, HAN)
HAN2ZEN = str.maketrans(HAN, ZEN)

# 全角から半角
print(ZEN.translate(ZEN2HAN))
# 結果
# !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~

# 半角から全角
print(HAN.translate(HAN2ZEN))
# 結果
# ！＂＃＄％＆＇（）＊＋，－．／０１２３４５６７８９：；＜＝＞？＠ＡＢＣＤＥＦＧＨＩＪＫＬＭＮＯＰＱＲＳＴＵＶＷＸＹＺ［＼］＾＿｀ａｂｃｄｅｆｇｈｉｊｋｌｍｎｏｐｑｒｓｔ
```

## chr()

入力されたUnicodeコードに対応する文字列を返します
https://docs.python.org/ja/3/library/functions.html?highlight=chr#chr

```{Python}

chr(0x0021)

# '!'
```


# 変換辞書用データについて

## 半角データ

Unicode表に従い、「!」(0x0021)から94文字を半角データとして使用します
https://ja.wikipedia.org/wiki/Unicode%E4%B8%80%E8%A6%A7_0000-0FFF

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

## 全角データ

Unicode表に従い、「！」(0xFF01)から94文字を半角データとして使用します
https://ja.wikipedia.org/wiki/Unicode%E4%B8%80%E8%A6%A7_F000-FFFF

```{Python}
"".join(chr(0xFF01 + c) for c in range(94))
# '！＂＃＄％＆＇（）＊＋，－．／０１２３４５６７８９：；＜＝＞？＠ＡＢＣＤＥＦＧＨＩＪＫＬＭＮＯＰＱＲＳＴＵＶＷＸＹＺ［＼］＾＿｀ａｂｃｄｅｆｇｈｉｊｋｌｍｎｏｐｑｒｓｔｕｖｗｘｙｚ｛｜｝～'
```

# 内包表記
## リスト内包表記

for 文の結果を用いてリストを作成する処理を一行にまとめたもの
https://qiita.com/y__sama/items/a2c458de97c4aa5a98e7

```{Python}
# リスト内包表記の例
[x * 2 for x in range(5)]

# 結果
# [0, 2, 4, 6, 8]
```

## 辞書内包表記

for 文の結果で辞書を作成する処理を一行にまとめたもの

```{Python}
# 辞書内包表記の例
{"no%d" % i: i for i in range(5)}

# 結果
# {'no0': 0, 'no1': 1, 'no2': 2, 'no3': 3, 'no4': 4}
```

## ジェネレータ内包表記

（私の理解不足のため）正確に説明できないので次のURLなど参照。
https://qiita.com/keitakurita/items/5a31b902db6adfa45a70