Fundamentals 9 min read

Converting Full‑Width and Half‑Width Characters in Python

This article explains the Unicode mapping between full‑width and half‑width characters, demonstrates simple Python functions for converting between them, and provides a flexible dictionary‑based approach for custom text conversion tasks.

Python Programming Learning Circle
Python Programming Learning Circle
Python Programming Learning Circle
Converting Full‑Width and Half‑Width Characters in Python

When processing text, mismatched full‑width and half‑width characters often cause problems, so a program is needed to convert them quickly.

The Unicode ranges are simple: full‑width characters are 0xFF01‑0xFF5E (decimal 65281‑65374), half‑width characters are 0x21‑0x7E (decimal 33‑126), and the space characters are 0x3000 (full‑width) and 0x20 (half‑width). Apart from the space, each half‑width code plus 65248 equals its full‑width counterpart.

Useful built‑in functions include chr() (returns a character for an integer 0‑255), unichr() (returns a Unicode character), and ord() (returns the integer code point of a character).

Example of printing the mapping:

<code>for i in xrange(33,127):
    print i, chr(i), i+65248, unichr(i+65248)
</code>

Simple conversion functions:

<code>def full2half(s):
    n = []
    s = s.decode('utf-8')
    for char in s:
        num = ord(char)
        if num == 0x3000:
            num = 32
        elif 0xFF01 <= num <= 0xFF5E:
            num -= 0xFEE0
        n.append(unichr(num))
    return ''.join(n)
</code>
<code>def half2full(s):
    n = []
    s = s.decode('utf-8')
    for char in s:
        num = ord(char)
        if num == 32:
            num = 0x3000
        elif 0x21 <= num <= 0x7E:
            num += 0xFEE0
        n.append(unichr(num))
    return ''.join(n)
</code>

In real scenarios you may need selective conversion, e.g., converting letters and numbers to half‑width while keeping punctuation full‑width. This can be achieved with custom mapping dictionaries:

<code>#!/usr/bin/env python
# -*- coding: utf-8 -*-

FH_SPACE = ((u" ", u" "),)
FH_NUM = ((u"0", u"0"), (u"1", u"1"), (u"2", u"2"), (u"3", u"3"), (u"4", u"4"),
          (u"5", u"5"), (u"6", u"6"), (u"7", u"7"), (u"8", u"8"), (u"9", u"9"))
FH_ALPHA = ((u"a", u"a"), (u"b", u"b"), (u"c", u"c"), (u"d", u"d"), (u"e", u"e"),
            (u"f", u"f"), (u"g", u"g"), (u"h", u"h"), (u"i", u"i"), (u"j", u"j"),
            (u"k", u"k"), (u"l", u"l"), (u"m", u"m"), (u"n", u"n"), (u"o", u"o"),
            (u"p", u"p"), (u"q", u"q"), (u"r", u"r"), (u"s", u"s"), (u"t", u"t"),
            (u"u", u"u"), (u"v", u"v"), (u"w", u"w"), (u"x", u"x"), (u"y", u"y"), (u"z", u"z"),
            (u"A", u"A"), (u"B", u"B"), (u"C", u"C"), (u"D", u"D"), (u"E", u"E"),
            (u"F", u"F"), (u"G", u"G"), (u"H", u"H"), (u"I", u"I"), (u"J", u"J"),
            (u"K", u"K"), (u"L", u"L"), (u"M", u"M"), (u"N", u"N"), (u"O", u"O"),
            (u"P", u"P"), (u"Q", u"Q"), (u"R", u"R"), (u"S", u"S"), (u"T", u"T"),
            (u"U", u"U"), (u"V", u"V"), (u"W", u"W"), (u"X", u"X"), (u"Y", u"Y"), (u"Z", u"Z"))
FH_PUNCTUATION = ((u".", u"."), (u",", u","), (u"!", u"!"), (u"?", u"?"), (u"”", u'"'),
                  (u"’", u"'"), (u"‘", u"`"), (u"@", u"@"), (u"_", u"_"), (u":", u":"),
                  (u";", u";"), (u"#", u"#"), (u"$", u"$"), (u"%", u"%"), (u"&", u"&"),
                  (u"(", u"("), (u")", u")"), (u"‐", u"-"), (u"=", u"="), (u"*", u"*"),
                  (u"+", u"+"), (u"-", u"-"), (u"/", u"/"), (u"<", u"<"), (u">", u">"),
                  (u"[", u"["), (u"¥", u"\\"), (u"]", u"]"), (u"^", u"^"), (u"{", u"{"),
                  (u"|", u"|"), (u"}", u"}"), (u"~", u"~"))

FH_ASCII = lambda: ((fr, to) for m in (FH_ALPHA, FH_NUM, FH_PUNCTUATION) for fr, to in m)

def convert(text, *maps, **ops):
    """Full‑width / half‑width conversion.
    Args:
        text: unicode string to convert
        maps: conversion maps
        skip: characters to skip (tuple or string)
    Returns:
        converted unicode string
    """
    if "skip" in ops:
        skip = ops["skip"]
        if isinstance(skip, basestring):
            skip = tuple(skip)
        def replace(t, fr, to):
            return t if fr in skip else t.replace(fr, to)
    else:
        def replace(t, fr, to):
            return t.replace(fr, to)
    for m in maps:
        if callable(m):
            m = m()
        elif isinstance(m, dict):
            m = m.items()
        for fr, to in m:
            text = replace(text, fr, to)
    return text

if __name__ == '__main__':
    text = u"成田空港—【JR特急成田エクスプレス号・横浜行,2站】—東京—【JR新幹線はやぶさ号・新青森行,6站 】—新青森—【JR特急スーパー白鳥号・函館行,4站 】—函館"
    print(convert(text, FH_ASCII, {u"【": u"[", u"】": u"]", u",": u",", u".": u"。", u"?": u"?", u"!": u"!"}, skip=",。?!“”"))
</code>

Note: In English typography, quotation marks are not distinguished as opening or closing quotes.

PythonUnicodestring processingFull-widthHalf-width
Python Programming Learning Circle
Written by

Python Programming Learning Circle

A global community of Chinese Python developers offering technical articles, columns, original video tutorials, and problem sets. Topics include web full‑stack development, web scraping, data analysis, natural language processing, image processing, machine learning, automated testing, DevOps automation, and big data.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.