Warning: Undefined variable $position in /home/pystyles/pystyle.info/public_html/wp/wp-content/themes/lionblog/functions.php on line 4897

Python – 全角英数字を半角に変換する方法について

Python – 全角英数字を半角に変換する方法について

概要

Python で日本語を扱う上で全角、半角の英数字や記号を効率的に扱うためのコード例を紹介します。

Advertisement

全角文字の Unicode コードポイント

Python では文字はすべて Unicode で扱われます。Unicode で規定されている文字1つ1つには、一意の数値が割り振られています。この数値を符号点 (code point) といいます。Python で全角文字を扱うには、まず Unicode 上でそれらの文字にどのような符号点が割り当てられているかを知る必要があります。 Python ではビルドインコマンドの chr() で文字から符号点 (16進数)、ord() で符号点から文字に変換できます。

In [1]:
# 符号点 (数値) -> 文字
print(chr(12354))  # 10進数 -> 文字
print(chr(0x3042))  # 16進数 -> 文字
あ
あ
In [2]:
# 文字 -> 数値
print(ord("あ"))  # 文字 -> 10進数
print(hex(ord("あ")))  # 文字 -> 16進数
12354
0x3042

アルファベット (半角/全角)

半角アルファベットは、Ascii コードは Unicode のサブセットになっているため、以下の範囲で定義されています。

  • 0x41 ~ 0x5A(A ~ Z): 半角大文字アルファベット
  • 0x61 ~ 0x7A(a ~ z): 半角小文字アルファベット
In [3]:
codes = list(range(0x41, 0x5A + 1)) + list(range(0x61, 0x7A + 1))  # A ~ Z a ~ z

for i, code in enumerate(codes, 1):
    print(f"{code:04X}: {chr(code)}", end="\n" if i % 6 == 0 else " ")
0041: A 0042: B 0043: C 0044: D 0045: E 0046: F
0047: G 0048: H 0049: I 004A: J 004B: K 004C: L
004D: M 004E: N 004F: O 0050: P 0051: Q 0052: R
0053: S 0054: T 0055: U 0056: V 0057: W 0058: X
0059: Y 005A: Z 0061: a 0062: b 0063: c 0064: d
0065: e 0066: f 0067: g 0068: h 0069: i 006A: j
006B: k 006C: l 006D: m 006E: n 006F: o 0070: p
0071: q 0072: r 0073: s 0074: t 0075: u 0076: v
0077: w 0078: x 0079: y 007A: z 

全角アルファベットは Unicode の The Unicode Standard, Version 15.0 – Halfwidth Katakana 領域のうち、以下の範囲で定義されています。

  • 0xFF21 ~ 0xFF3A( ~ ): 全角大文字アルファベット
  • 0xFF41 ~ 0xFF5A( ~ ): 全角小文字アルファベット
In [4]:
codes = list(range(0xFF21, 0xFF3A + 1)) + list(
    range(0xFF41, 0xFF5A + 1)
)  # A ~ Z a ~ z

for i, code in enumerate(codes, 1):
    print(f"{code:04X}: {chr(code)}", end="\n" if i % 6 == 0 else " ")
FF21: A FF22: B FF23: C FF24: D FF25: E FF26: F
FF27: G FF28: H FF29: I FF2A: J FF2B: K FF2C: L
FF2D: M FF2E: N FF2F: O FF30: P FF31: Q FF32: R
FF33: S FF34: T FF35: U FF36: V FF37: W FF38: X
FF39: Y FF3A: Z FF41: a FF42: b FF43: c FF44: d
FF45: e FF46: f FF47: g FF48: h FF49: i FF4A: j
FF4B: k FF4C: l FF4D: m FF4E: n FF4F: o FF50: p
FF51: q FF52: r FF53: s FF54: t FF55: u FF56: v
FF57: w FF58: x FF59: y FF5A: z 

数宇 (半角/全角)

半角数字は、Ascii コードは Unicode のサブセットになっているため、以下の範囲で定義されています。

  • 0x30 ~ 0x39(0 ~ 9): 半角数字
In [5]:
codes = list(range(0x30, 0x39 + 1))  # 0 ~ 9

for i, code in enumerate(codes, 1):
    print(f"{code:04X}: {chr(code)}", end="\n" if i % 6 == 0 else " ")
0030: 0 0031: 1 0032: 2 0033: 3 0034: 4 0035: 5
0036: 6 0037: 7 0038: 8 0039: 9 

全角数字は Unicode の The Unicode Standard, Version 15.0 – Halfwidth Katakana 領域のうち、以下の範囲で定義されています。

  • 0xFF10 ~ 0xFF19( ~ ): 全角数字
In [6]:
codes = list(range(0xFF10, 0xFF19 + 1))  # 0 ~ 9

for i, code in enumerate(codes, 1):
    print(f"{code:04X}: {chr(code)}", end="\n" if i % 6 == 0 else " ")
FF10: 0 FF11: 1 FF12: 2 FF13: 3 FF14: 4 FF15: 5
FF16: 6 FF17: 7 FF18: 8 FF19: 9 

記号

半角記号は、Ascii コードは Unicode のサブセットになっているため、以下の範囲で定義されています。

  • 0x21 ~ 0x2F(! " # $ % & ' ( ) * + , - . /)
  • 0x3A ~ 0x40(: ; < = > ? @)
  • 0x5B ~ 0x60([ \ ] ^ _ `)
  • 0x7B ~ 0x7E({ | } ~)
In [7]:
codes = (
    list(range(0x21, 0x2F + 1))  # `!` `"` `#` `$` `%` `&` `'` `(` `)` `*` `+` `,` `-` `.` `/`
    + list(range(0x3A, 0x40 + 1))  # `:` `;` `<` `=` `>` `?` `@`
    + list(range(0x5B, 0x60 + 1))  # `[` `\` `]` `^` `_` ``` ` ```
    + list(range(0x7B, 0x7E + 1))  # `{` `|` `}` `~`
)

for i, code in enumerate(codes, 1):
    print(f"{code:04X}: {chr(code)}", end="\n" if i % 6 == 0 else " ")
0021: ! 0022: " 0023: # 0024: $ 0025: % 0026: &
0027: ' 0028: ( 0029: ) 002A: * 002B: + 002C: ,
002D: - 002E: . 002F: / 003A: : 003B: ; 003C: <
003D: = 003E: > 003F: ? 0040: @ 005B: [ 005C: \
005D: ] 005E: ^ 005F: _ 0060: ` 007B: { 007C: |
007D: } 007E: ~ 

全角記号は Unicode の The Unicode Standard, Version 15.0 – Hiragana 及び The Unicode Standard, Version 15.0 – Halfwidth Katakana 領域のうち、以下の範囲で定義されています。

  • Ascii コードに半角記号がある全角記号
    • 0xFF01 ~ 0xFF0F( )
    • 0xFF1A ~ 0xFF20( )
    • 0xFF3B ~ 0xFF40( _ )
    • 0xFF5B ~ 0xFF5E( )
  • Ascii コードに半角記号がない全角記号
    • 0xFF5F, 0xFF60( )
  • 句読点
    • 0xFF61, 0xFF64( )
In [8]:
codes = (
    list(range(0xFF01, 0xFF0F + 1))  # `!` `"` `#` `$` `%` `&` `'` `(` `)` `*` `+` `,` `-` `.` `/`
    + list(range(0xFF1A, 0xFF20 + 1))  # `:` `;` `<` `=` `>` `?` `@`
    + list(range(0xFF3B, 0xFF40 + 1))  # `[` `\` `]` `^` `_` ```
    + list(range(0xFF5B, 0xFF60 + 1))  # `{` `|` `}` `~`
    + list(range(0xFF5F, 0xFF5E + 1))  # `⦅` `⦆`
    + list(range(0xFF61, 0xFF64 + 1))  # `。` `「` `」` `、`
)

for i, code in enumerate(codes, 1):
    print(f"{code:04X}: {chr(code)}", end="\n" if i % 6 == 0 else " ")
FF01: ! FF02: " FF03: # FF04: $ FF05: % FF06: &
FF07: ' FF08: ( FF09: ) FF0A: * FF0B: + FF0C: ,
FF0D: - FF0E: . FF0F: / FF1A: : FF1B: ; FF1C: <
FF1D: = FF1E: > FF1F: ? FF20: @ FF3B: [ FF3C: \
FF3D: ] FF3E: ^ FF3F: _ FF40: ` FF5B: { FF5C: |
FF5D: } FF5E: ~ FF5F: ⦅ FF60: ⦆ FF61: 。 FF62: 「
FF63: 」 FF64: 、 

ひらがな

ひらがなは Unicode の The Unicode Standard, Version 15.0 – Hiragana 領域のうち、以下の範囲で定義されています。

In [9]:
codes = list(range(0x3041, 0x3096 + 1)) + list(range(0x3099, 0x309E + 1))

for i, code in enumerate(codes, 1):
    print(f"{code:04X}: {chr(code)}", end="\n" if i % 6 == 0 else " ")
3041: ぁ 3042: あ 3043: ぃ 3044: い 3045: ぅ 3046: う
3047: ぇ 3048: え 3049: ぉ 304A: お 304B: か 304C: が
304D: き 304E: ぎ 304F: く 3050: ぐ 3051: け 3052: げ
3053: こ 3054: ご 3055: さ 3056: ざ 3057: し 3058: じ
3059: す 305A: ず 305B: せ 305C: ぜ 305D: そ 305E: ぞ
305F: た 3060: だ 3061: ち 3062: ぢ 3063: っ 3064: つ
3065: づ 3066: て 3067: で 3068: と 3069: ど 306A: な
306B: に 306C: ぬ 306D: ね 306E: の 306F: は 3070: ば
3071: ぱ 3072: ひ 3073: び 3074: ぴ 3075: ふ 3076: ぶ
3077: ぷ 3078: へ 3079: べ 307A: ぺ 307B: ほ 307C: ぼ
307D: ぽ 307E: ま 307F: み 3080: む 3081: め 3082: も
3083: ゃ 3084: や 3085: ゅ 3086: ゆ 3087: ょ 3088: よ
3089: ら 308A: り 308B: る 308C: れ 308D: ろ 308E: ゎ
308F: わ 3090: ゐ 3091: ゑ 3092: を 3093: ん 3094: ゔ
3095: ゕ 3096: ゖ 3099: ゙ 309A: ゚ 309B: ゛ 309C: ゜
309D: ゝ 309E: ゞ 

カタカナ (半角/全角)

全角カタカナは Unicode の The Unicode Standard, Version 15.0 – Katakana 領域のうち、以下の範囲で定義されています。

In [10]:
codes = list(range(0x30A1, 0x30FE + 1)) + list(range(0x3099, 0x309C + 1))

for i, code in enumerate(codes, 1):
    print(f"{code:04X}: {chr(code)}", end="\n" if i % 6 == 0 else " ")
30A1: ァ 30A2: ア 30A3: ィ 30A4: イ 30A5: ゥ 30A6: ウ
30A7: ェ 30A8: エ 30A9: ォ 30AA: オ 30AB: カ 30AC: ガ
30AD: キ 30AE: ギ 30AF: ク 30B0: グ 30B1: ケ 30B2: ゲ
30B3: コ 30B4: ゴ 30B5: サ 30B6: ザ 30B7: シ 30B8: ジ
30B9: ス 30BA: ズ 30BB: セ 30BC: ゼ 30BD: ソ 30BE: ゾ
30BF: タ 30C0: ダ 30C1: チ 30C2: ヂ 30C3: ッ 30C4: ツ
30C5: ヅ 30C6: テ 30C7: デ 30C8: ト 30C9: ド 30CA: ナ
30CB: ニ 30CC: ヌ 30CD: ネ 30CE: ノ 30CF: ハ 30D0: バ
30D1: パ 30D2: ヒ 30D3: ビ 30D4: ピ 30D5: フ 30D6: ブ
30D7: プ 30D8: ヘ 30D9: ベ 30DA: ペ 30DB: ホ 30DC: ボ
30DD: ポ 30DE: マ 30DF: ミ 30E0: ム 30E1: メ 30E2: モ
30E3: ャ 30E4: ヤ 30E5: ュ 30E6: ユ 30E7: ョ 30E8: ヨ
30E9: ラ 30EA: リ 30EB: ル 30EC: レ 30ED: ロ 30EE: ヮ
30EF: ワ 30F0: ヰ 30F1: ヱ 30F2: ヲ 30F3: ン 30F4: ヴ
30F5: ヵ 30F6: ヶ 30F7: ヷ 30F8: ヸ 30F9: ヹ 30FA: ヺ
30FB: ・ 30FC: ー 30FD: ヽ 30FE: ヾ 3099: ゙ 309A: ゚
309B: ゛ 309C: ゜ 

半角カタカナは Unicode の The Unicode Standard, Version 15.0 – Halfwidth Katakana 領域のうち、以下の範囲で定義されています。

  • 0xFF65(): 中黒 (半角)
  • 0xFF66 ~ 0xFF9D( ~ 及び ): 半角カタカナ、長音記号 (半角)
  • 濁点
In [11]:
codes = list(range(0xFF65, 0xFF9F + 1))

for i, code in enumerate(codes, 1):
    print(f"{code:04X}: {chr(code)}", end="\n" if i % 6 == 0 else " ")
FF65: ・ FF66: ヲ FF67: ァ FF68: ィ FF69: ゥ FF6A: ェ
FF6B: ォ FF6C: ャ FF6D: ュ FF6E: ョ FF6F: ッ FF70: ー
FF71: ア FF72: イ FF73: ウ FF74: エ FF75: オ FF76: カ
FF77: キ FF78: ク FF79: ケ FF7A: コ FF7B: サ FF7C: シ
FF7D: ス FF7E: セ FF7F: ソ FF80: タ FF81: チ FF82: ツ
FF83: テ FF84: ト FF85: ナ FF86: ニ FF87: ヌ FF88: ネ
FF89: ノ FF8A: ハ FF8B: ヒ FF8C: フ FF8D: ヘ FF8E: ホ
FF8F: マ FF90: ミ FF91: ム FF92: メ FF93: モ FF94: ヤ
FF95: ユ FF96: ヨ FF97: ラ FF98: リ FF99: ル FF9A: レ
FF9B: ロ FF9C: ワ FF9D: ン FF9E: ゙ FF9F: ゚ 

漢字

漢字は文字数が多くどこまで含むか難しいですが、The Unicode Standard, Version 15.0 – CJK統合漢字 で定義している 0x4E00-0x9FFF の範囲を漢字とするのが一つの方法であると思います。

In [12]:
codes = list(range(0x4E00, 0x9FFF + 1))

# 数が多すぎるので、最初の50文字だけ表示します。日本、韓国、中国で使用される漢字が統合されているので、日本では見かけない漢字も含まれます。
for i, code in enumerate(codes[:50], 1):
    print(f"{code:04X}: {chr(code)}", end="\n" if i % 6 == 0 else " ")
4E00: 一 4E01: 丁 4E02: 丂 4E03: 七 4E04: 丄 4E05: 丅
4E06: 丆 4E07: 万 4E08: 丈 4E09: 三 4E0A: 上 4E0B: 下
4E0C: 丌 4E0D: 不 4E0E: 与 4E0F: 丏 4E10: 丐 4E11: 丑
4E12: 丒 4E13: 专 4E14: 且 4E15: 丕 4E16: 世 4E17: 丗
4E18: 丘 4E19: 丙 4E1A: 业 4E1B: 丛 4E1C: 东 4E1D: 丝
4E1E: 丞 4E1F: 丟 4E20: 丠 4E21: 両 4E22: 丢 4E23: 丣
4E24: 两 4E25: 严 4E26: 並 4E27: 丧 4E28: 丨 4E29: 丩
4E2A: 个 4E2B: 丫 4E2C: 丬 4E2D: 中 4E2E: 丮 4E2F: 丯
4E30: 丰 4E31: 丱 

空白

  • 0x20: 半角スペース
  • 0x3000: 全角スペース
  • 0x3000(\t): 水平タブ
  • 0x3000(\v): 垂直タブ
  • 0x3000(\r): 復帰
  • 0x3000(\n): 改行
  • 0x3000(\f): 書式送り

全角文字を扱う正規表現

正規表現では、符号点上に連続した文字は範囲で指定できるので (例: [abcde][a-e] とかける)、これを使ってひらがなやかたかな1文字を表す正規表現を作成できます。

  • 半角文字
    • 半角アルファベット1文字: [a-zA-Z] または [\u0061-\u007A\u0041-\u005A]
    • 半角小文字アルファベット1文字: [a-z] または [\u0061-\u007A]
    • 半角大文字アルファベット1文字: [A-Z] または [\u0041-\u005A]
    • 半角数字1文字: [0-9] または [\u0030-\u0039] または \d
  • 全角文字
    • 全角アルファベット1文字: [A-Za-z] または [\uFF21-\uFF3A\uFF41-\uFF5A]
    • 全角小文字アルファベット1文字: [a-z] または [\uFF41-\uFF5A]
    • 全角大文字アルファベット1文字: [A-Z] または [\uFF21-\uFF3A]
    • 全角数字1文字: [0-9] または [\uFF10-\uFF19]
    • ひらがな1文字 ( 及び濁点を含む): ([\u3041-\u3096][\u3099-\u309E]?)
    • 全角カタカナ1文字 ( 及び濁点を含む): ([\u30A1-\u30FF][\u3099-\u309E]?)
    • 半角カタカナ1文字 ( 及び濁点を含む): [\uFF65-\uFF9F]
    • 漢字1文字: [0x4E00-0x9FFF]
In [13]:
import re

s = "HELLO world ハロー・ワールド ひらがな ダウンロード 漢字 007"

# re.search() は最初にマッチしたものを返します。

# 1文字以上の連続したひらがなを抽出
print(re.search("([\u3041-\u3096][\u3099-\u309E]?)+", s))

# 1文字以上の連続したカタカナを抽出
print(re.search("([\u30A1-\u30FF][\u3099-\u309E]?)+", s))

# 1文字以上の連続した全角英字を抽出
print(re.search("[\uFF21-\uFF3A\uFF41-\uFF5A]+", s))

# 1文字以上の連続した全角数字を抽出
print(re.search("[\uFF10-\uFF19]+", s))

# 1文字以上の連続した全角大文字英字を抽出
print(re.search("[\uFF21-\uFF3A]+", s))

# 1文字以上の連続した全角小文字英字を抽出
print(re.search("[\uFF41-\uFF5A]+", s))

# 1文字以上の連続した半角カタカナを抽出
print(re.search("[\uFF65-\uFF9F]+", s))

# 1文字以上の連続した漢字を抽出
print(re.search("[\u4E00-\u9FFF]+", s))
<re.Match object; span=(22, 26), match='ひらがな'>
<re.Match object; span=(12, 21), match='ハロー・ワールド'>
<re.Match object; span=(0, 5), match='HELLO'>
<re.Match object; span=(39, 42), match='007'>
<re.Match object; span=(0, 5), match='HELLO'>
<re.Match object; span=(6, 11), match='world'>
<re.Match object; span=(27, 35), match='ダウンロード'>
<re.Match object; span=(36, 38), match='漢字'>

全角のアルファベット、数字を半角に変換する

  1. str.maketrans(x, y)x に変換前の文字の一覧、y にそれに対応する変換後の文字の一覧を渡して、str.translate() に渡せる形式の変換辞書にします。
  2. str.translate() で変換辞書に基づいて、変換します。

最初の「変換前の文字がキー、変換後の文字が値の dict」を作成する際に先ほどの Unicode の符号点の知識を使います。

In [14]:
# 全角の文字列
FULLWIDTH_DIGITS = "0123456789"
FULLWIDTH_ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
FULLWIDTH_PUNCTUATION = "!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ "
FULLWIDTH_ALPHANUMERIC = FULLWIDTH_DIGITS + FULLWIDTH_ALPHABET  # 英数字
FULLWIDTH_ALL = FULLWIDTH_ALPHANUMERIC + FULLWIDTH_PUNCTUATION  # 英数字、記号

# 半角の文字列
HALFWIDTH_DIGITS = "0123456789"
HALFWIDTH_ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
HALFWIDTH_PUNCTUATION = "!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ "
HALFWIDTH_ALPHANUMERIC = HALFWIDTH_DIGITS + HALFWIDTH_ALPHABET  # 英数字
HALFWIDTH_ALL = HALFWIDTH_ALPHANUMERIC + HALFWIDTH_PUNCTUATION  # 英数字、記号
In [15]:
# 数字、アルファベットを半角に変換する。
conv_map = str.maketrans(FULLWIDTH_ALPHANUMERIC, HALFWIDTH_ALPHANUMERIC)

before = "Alphabet"
after = before.translate(conv_map)

print(before)
print(after)
Alphabet
Alphabet
In [16]:
# 全角の記号、数字、アルファベットを半角に変換する。
conv_map = str.maketrans(FULLWIDTH_ALL, HALFWIDTH_ALL)

before = "Alphabet (012)"
after = before.translate(conv_map)

print(before)
print(after)
Alphabet (012)
Alphabet (012)

半角のアルファベット、数字を全角に変換する

str.maketrans() に渡す引数を逆にするだけで実現できます。`

In [17]:
# 全角の文字列
FULLWIDTH_DIGITS = "0123456789"
FULLWIDTH_ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
FULLWIDTH_PUNCTUATION = "!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ "
FULLWIDTH_ALPHANUMERIC = FULLWIDTH_DIGITS + FULLWIDTH_ALPHABET  # 英数字
FULLWIDTH_ALL = FULLWIDTH_ALPHANUMERIC + FULLWIDTH_PUNCTUATION  # 英数字、記号

# 半角の文字列
HALFWIDTH_DIGITS = "0123456789"
HALFWIDTH_ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
HALFWIDTH_PUNCTUATION = "!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ "
HALFWIDTH_ALPHANUMERIC = HALFWIDTH_DIGITS + HALFWIDTH_ALPHABET  # 英数字
HALFWIDTH_ALL = HALFWIDTH_ALPHANUMERIC + HALFWIDTH_PUNCTUATION  # 英数字、記号

# 数字、アルファベットを半角に変換する。
conv_map = str.maketrans(HALFWIDTH_ALPHANUMERIC, FULLWIDTH_ALPHANUMERIC)

before = "Alphabet"
after = before.translate(conv_map)

print(before)
print(after)
Alphabet
Alphabet
In [18]:
# 全角の記号、数字、アルファベットを半角に変換する。
conv_map = str.maketrans(HALFWIDTH_ALL, FULLWIDTH_ALL)

before = "Alphabet (012)"
after = before.translate(conv_map)

print(before)
print(after)
Alphabet (012)
Alphabet (012)

半角のカタカナを全角に変換する

全角カタカナ、半角カタカナは濁点を含む文字が1対1対応していないので、少々面倒です。 例えば、全角文字 は Unicode で1文字で表せますが、半角カタカナには1文字の は存在しないため、 + 濁点(\uFF9E) の2文字で表す必要があります。

In [19]:
# fmt: off
KANA_DAKUTEN_HALF_TO_FULL_MAP = {"カ": "ガ", "キ": "ギ", "ク": "グ", "ケ": "ゲ", "コ": "ゴ", "サ": "ザ", "シ": "ジ", "ス": "ズ", "セ": "ゼ", "ソ": "ゾ", "タ": "ダ", "チ": "ヂ", "ツ": "ヅ", "テ": "デ", "ト": "ド", "ハ": "バ", "ヒ": "ビ", "フ": "ブ", "ヘ": "ベ", "ホ": "ボ", "ワ": "ヷ", "ヲ": "ヺ"}
KANA_HANDAKUTEN_HALF_TO_FULL_MAP = {"ハ": "パ", "ヒ": "ピ", "フ": "プ", "ヘ": "ペ", "ホ": "ポ"}
KANA_HALF_TO_FULL_MAP = {"ア": "ア", "イ": "イ", "ウ": "ウ", "エ": "エ", "オ": "オ", "カ": "カ", "キ": "キ", "ク": "ク", "ケ": "ケ", "コ": "コ", "サ": "サ", "シ": "シ", "ス": "ス", "セ": "セ", "ソ": "ソ", "タ": "タ", "チ": "チ", "ツ": "ツ", "テ": "テ", "ト": "ト", "ナ": "ナ", "ニ": "ニ", "ヌ": "ヌ", "ネ": "ネ", "ノ": "ノ", "ハ": "ハ", "ヒ": "ヒ", "フ": "フ", "ヘ": "ヘ", "ホ": "ホ", "マ": "マ", "ミ": "ミ", "ム": "ム", "メ": "メ", "モ": "モ", "ヤ": "ヤ", "ユ": "ユ", "ヨ": "ヨ", "ラ": "ラ", "リ": "リ", "ル": "ル", "レ": "レ", "ロ": "ロ", "ワ": "ワ", "ヲ": "ヲ", "ン": "ン", "ァ": "ァ", "ィ": "ィ", "ゥ": "ゥ", "ェ": "ェ", "ォ": "ォ", "ャ": "ャ", "ュ": "ュ", "ョ": "ョ", "ッ": "ッ", "・": "・", "ー": "ー"}
HALF_DAKUTEN = "\uFF9E"
HALF_HANDAKUTEN = "\uFF9F"
FULL_DAKUTEN = "\uFF9E"
FULL_HANDAKUTEN = "\u309A"
# fmt: on


def full_to_half_kana(text):
    ret = ""

    import itertools

    for c1, c2 in itertools.zip_longest(text, text[1:]):
        if not (0xFF65 <= ord(c1) <= 0xFF9F):
            continue  # 変換対象でない

        if c2 and c2 == HALF_DAKUTEN:
            # 半角カタカナ + 濁点 -> 全角カタカナ + 濁点
            ret += KANA_DAKUTEN_HALF_TO_FULL_MAP.get(
                c1, KANA_HALF_TO_FULL_MAP[c1] + FULL_DAKUTEN
            )
        elif c2 and c2 == HALF_HANDAKUTEN:
            # 半角カタカナ + 半濁点 -> 全角カタカナ + 半濁点
            ret += KANA_HANDAKUTEN_HALF_TO_FULL_MAP.get(
                c1, KANA_HALF_TO_FULL_MAP[c1] + FULL_HANDAKUTEN
            )
        elif c1 in HALF_DAKUTEN + HALF_HANDAKUTEN:
            continue  # 濁点、半濁点は無視
        else:
            ret += KANA_HALF_TO_FULL_MAP[c1]

    return ret


print(half_to_full_kana("アップロード・ダウンロード"))
アップロード・ダウンロード

全角のカタカナを半角に変換する

ヮヽヾ のような対応する半角カタカナがない全角カタカナは変換されません。

In [20]:
# fmt: off
KANA_DAKUTEN_FULL_TO_HALF_MAP = {'ガ': 'カ', 'ギ': 'キ', 'グ': 'ク', 'ゲ': 'ケ', 'ゴ': 'コ', 'ザ': 'サ', 'ジ': 'シ', 'ズ': 'ス', 'ゼ': 'セ', 'ゾ': 'ソ', 'ダ': 'タ', 'ヂ': 'チ', 'ヅ': 'ツ', 'デ': 'テ', 'ド': 'ト', 'バ': 'ハ', 'ビ': 'ヒ', 'ブ': 'フ', 'ベ': 'ヘ', 'ボ': 'ホ', 'ヷ': 'ワ', 'ヺ': 'ヲ'}
KANA_HANDAKUTEN_FULL_TO_HALF_MAP = {'パ': 'ハ', 'ピ': 'ヒ', 'プ': 'フ', 'ペ': 'ヘ', 'ポ': 'ホ'}
KANA_FULL_TO_HALF_MAP = {'ア': 'ア',  'イ': 'イ',  'ウ': 'ウ',  'エ': 'エ',  'オ': 'オ',  'カ': 'カ',  'キ': 'キ',  'ク': 'ク',  'ケ': 'ケ',  'コ': 'コ',  'サ': 'サ',  'シ': 'シ',  'ス': 'ス',  'セ': 'セ',  'ソ': 'ソ',  'タ': 'タ',  'チ': 'チ',  'ツ': 'ツ',  'テ': 'テ',  'ト': 'ト',  'ナ': 'ナ',  'ニ': 'ニ',  'ヌ': 'ヌ',  'ネ': 'ネ',  'ノ': 'ノ',  'ハ': 'ハ',  'ヒ': 'ヒ',  'フ': 'フ',  'ヘ': 'ヘ',  'ホ': 'ホ',  'マ': 'マ',  'ミ': 'ミ',  'ム': 'ム',  'メ': 'メ',  'モ': 'モ',  'ヤ': 'ヤ',  'ユ': 'ユ',  'ヨ': 'ヨ',  'ラ': 'ラ',  'リ': 'リ',  'ル': 'ル',  'レ': 'レ',  'ロ': 'ロ',  'ワ': 'ワ',  'ヲ': 'ヲ',  'ン': 'ン',  'ァ': 'ァ',  'ィ': 'ィ',  'ゥ': 'ゥ',  'ェ': 'ェ',  'ォ': 'ォ',  'ャ': 'ャ',  'ュ': 'ュ',  'ョ': 'ョ',  'ッ': 'ッ',  '・': '・',  'ー': 'ー'}
HALF_DAKUTEN = "\uFF9E"
HALF_HANDAKUTEN = "\uFF9F"
FULL_DAKUTEN = "\uFF9E"
FULL_HANDAKUTEN = "\u309A"
# fmt: on


def full_to_half_kana(text):
    ret = ""

    import itertools

    for c1, c2 in itertools.zip_longest(text, text[1:]):
        if not (0x30A1 <= ord(c1) <= 0x30FC):
            continue  # 変換対象でない

        if c2 == FULL_DAKUTEN:
            # 全角カタカナ + 濁点 -> 半角カタカナ + 濁点
            ret += KANA_FULL_TO_HALF_MAP[c1] + HALF_DAKUTEN
        elif c1 in KANA_DAKUTEN_FULL_TO_HALF_MAP:
            # 全角カタカナ + 濁点 -> 半角カタカナ + 濁点
            ret += KANA_DAKUTEN_FULL_TO_HALF_MAP[c1] + HALF_DAKUTEN
        elif c2 == FULL_HANDAKUTEN:
            # 全角カタカナ + 半濁点 -> 半角カタカナ + 半濁点
            ret += KANA_FULL_TO_HALF_MAP[c1] + HALF_HANDAKUTEN
        elif c1 in KANA_HANDAKUTEN_FULL_TO_HALF_MAP:
            # 全角カタカナ + 半濁点 -> 半角カタカナ + 半濁点
            ret += KANA_HANDAKUTEN_FULL_TO_HALF_MAP[c1] + HALF_HANDAKUTEN
        elif c1 in HALF_DAKUTEN + HALF_HANDAKUTEN:
            continue  # 濁点、半濁点は無視
        else:
            ret += KANA_FULL_TO_HALF_MAP[c1]

    return ret


print(full_to_half_kana("アップロード"))
アップロード