unicodedata 模块 - Unicode字符数据库处理

Python unicodedata模块详解,提供Unicode字符数据库访问功能,支持字符属性查询、字符分类、文本规范化等操作

分类: stdlib 难度: 中级 更新: 2024-01-15
Unicode 字符处理 文本处理 字符编码 国际化 字符分类

unicodedata 模块 - Unicode字符数据库处理

📝 概述

unicodedata模块提供了对Unicode字符数据库(UCD)的访问功能。UCD是Unicode字符数据库(Unicode Character DataBase)的缩写,由一些描述Unicode字符属性和内部关系的纯文本或html文件组成。该模块允许开发者查询字符的各种属性、进行字符分类、文本规范化等操作,是处理国际化文本的重要工具。

🎯 学习目标

  • 理解Unicode字符数据库的基本概念和结构
  • 掌握unicodedata模块的核心函数和使用方法
  • 学会查询字符的各种属性(名称、分类、数值等)
  • 掌握Unicode文本规范化技术
  • 了解字符编码和国际化文本处理的最佳实践

📋 前置知识

  • Python基本语法和字符串操作
  • Unicode编码和字符集的基本概念
  • 文本处理和国际化的基础知识
  • 异常处理机制

🔍 详细内容

Unicode字符数据库概述

UCD中的文本文件大都是适合于程序分析的Unicode相关数据。其中的html文件解释了数据库的组织,数据的格式和含义。

UCD中最庞大的文件无疑就是描述汉字属性的文件Unihan.txt。在UCD 5.0.0中,Unihan.txt文件大小有28,221K字节。Unihan.txt中包含了很多有参考价值的索引,例如汉字部首、笔划、拼音、使用频度、四角号码排序等。这些索引都是基于一些比较权威的辞典,但大多数索引只能检索部分汉字。

核心函数详解

字符名称查询

unicodedata.lookup(name) 通过名称来查找一个字符。如果字符存在就返回相应字符,如果不存在抛出异常KeyError。

import unicodedata

# 通过名称查找字符
print(unicodedata.lookup('LEFT CURLY BRACKET'))  # 输出: {
print(unicodedata.lookup('COMMERCIAL AT'))       # 输出: @

# 无效名称会抛出异常
try:
    print(unicodedata.lookup('LEFT'))
except KeyError as e:
    print(f"错误: {e}")  # 错误: "undefined character name 'LEFT'"

unicodedata.name(chr[, default]) 通过字符来查找它的名称。如果成功返回相应名称,否则抛出异常ValueError。

import unicodedata

# 通过字符获取名称
print(unicodedata.name('{'))  # 输出: LEFT CURLY BRACKET
print(unicodedata.name('@'))  # 输出: COMMERCIAL AT
print(unicodedata.name('中')) # 输出: CJK UNIFIED IDEOGRAPH-4E2D

# 多字符会抛出异常
try:
    print(unicodedata.name('{{'))
except TypeError as e:
    print(f"错误: {e}")

数值字符处理

unicodedata.decimal(chr[, default]) 返回表示数字字符的数值。如果给一个没有数字的值时,会抛出异常ValueError。

import unicodedata

# 获取数字字符的十进制值
print(unicodedata.decimal('7'))     # 输出: 7
print(unicodedata.decimal('九'))    # 输出: 9

# 使用默认值避免异常
print(unicodedata.decimal('a', -1)) # 输出: -1

unicodedata.digit(chr[, default]) 把一个合法的数字字符串转换为数字值,比如0到9的字符串转换为相应的数字值。

import unicodedata

# 获取数字字符的数字值
print(unicodedata.digit('9', None))  # 输出: 9
print(unicodedata.digit('②', None))  # 输出: 2

# 非单字符会抛出异常
try:
    print(unicodedata.digit('9a', None))
except TypeError as e:
    print(f"错误: {e}")

unicodedata.numeric(chr[, default]) 把一个表示数字的字符串转换为浮点数返回。与digit()不一样的地方是它可以处理任意表示数值的字符,不仅仅限于0到9的字符。

import unicodedata

# 获取各种数字字符的数值
print(unicodedata.numeric('四', None))  # 输出: 4.0
print(unicodedata.numeric('8', None))   # 输出: 8.0
print(unicodedata.numeric('½', None))   # 输出: 0.5
print(unicodedata.numeric('Ⅴ', None))   # 输出: 5.0

字符分类和属性

unicodedata.category(chr) 返回字符在Unicode中的分类类型。

代码 分类 描述
Cc Other, Control 控制字符
Cf Other, Format 格式字符
Cn Other, Not Assigned 未分配字符
Co Other, Private Use 私用字符
Cs Other, Surrogate 代理字符
Ll Letter, Lowercase 小写字母
Lm Letter, Modifier 修饰字母
Lo Letter, Other 其他字母
Lt Letter, Titlecase 标题字母
Lu Letter, Uppercase 大写字母
Mc Mark, Spacing Combining 间距组合标记
Me Mark, Enclosing 包围标记
Mn Mark, Nonspacing 非间距标记
Nd Number, Decimal Digit 十进制数字
Nl Number, Letter 字母数字
No Number, Other 其他数字
Pc Punctuation, Connector 连接标点
Pd Punctuation, Dash 破折号标点
Pe Punctuation, Close 关闭标点
Pf Punctuation, Final quote 结束引号
Pi Punctuation, Initial quote 开始引号
Po Punctuation, Other 其他标点
Ps Punctuation, Open 开放标点
Sc Symbol, Currency 货币符号
Sk Symbol, Modifier 修饰符号
Sm Symbol, Math 数学符号
So Symbol, Other 其他符号
Zl Separator, Line 行分隔符
Zp Separator, Paragraph 段落分隔符
Zs Separator, Space 空格分隔符
import unicodedata

# 查询字符分类
print(unicodedata.category('四'))  # 输出: Lo (Letter, Other)
print(unicodedata.category('8'))   # 输出: Nd (Number, Decimal Digit)
print(unicodedata.category('a'))   # 输出: Ll (Letter, Lowercase)
print(unicodedata.category('A'))   # 输出: Lu (Letter, Uppercase)
print(unicodedata.category('$'))   # 输出: Sc (Symbol, Currency)

unicodedata.bidirectional(chr) 返回字符的双向类别,用于确定从左到右还是从右到左的排列。

import unicodedata

# 查询字符的双向属性
print(unicodedata.bidirectional('9'))        # 输出: EN (European Number)
print(unicodedata.bidirectional('\u0660'))   # 输出: AN (Arabic Number)
print(unicodedata.bidirectional('中'))       # 输出: L (Letter)
print(unicodedata.bidirectional('a'))        # 输出: L (Letter)

其中:

  • EN表示European Number(欧洲数字)
  • AN表示Arabic Number(阿拉伯数字)
  • L表示Letter(字母)
  • R表示Right-to-Left(从右到左)

字符组合和显示属性

unicodedata.combining(chr) 返回字符的权威组合值,如果没有定义,默认返回0。在正规化操作时,可以根据这个值进行排序,大的值排在小的值后面。

import unicodedata

# 查询字符的组合类
print(unicodedata.combining('9'))   # 输出: 0
print(unicodedata.combining('A'))   # 输出: 0
print(unicodedata.combining('\u0300'))  # 输出: 230 (重音符)

unicodedata.east_asian_width(chr) 返回字符显示的宽度类别。

宽度类别:

  • ‘F’ (Fullwidth) - 全角
  • ‘H’ (Halfwidth) - 半角
  • ‘W’ (Wide) - 宽字符
  • ‘Na’ (Narrow) - 窄字符
  • ‘A’ (Ambiguous) - 不确定
  • ‘N’ (Natural) - 自然宽度
import unicodedata

# 查询字符显示宽度
print(unicodedata.east_asian_width('9'))   # 输出: Na (Narrow)
print(unicodedata.east_asian_width('A'))   # 输出: Na (Narrow)
print(unicodedata.east_asian_width('蔡'))  # 输出: W (Wide)
print(unicodedata.east_asian_width(' '))  # 输出: F (Fullwidth)

unicodedata.mirrored(chr) 判断一个字符是否支持镜像属性,如果支持返回1,否则返回0。

import unicodedata

# 查询字符镜像属性
print(unicodedata.mirrored('9'))    # 输出: 0
print(unicodedata.mirrored('A'))    # 输出: 0
print(unicodedata.mirrored('('))    # 输出: 1
print(unicodedata.mirrored('['))    # 输出: 1

字符分解和规范化

unicodedata.decomposition(chr) 返回字符的分解形式,如果不可分解,返回空字符串。

import unicodedata

# 查询字符分解
print(unicodedata.decomposition('9'))     # 输出: (空字符串)
print(unicodedata.decomposition('-'))     # 输出: (空字符串)
print(unicodedata.decomposition('蔡'))    # 输出: (空字符串)
print(unicodedata.decomposition('ガ'))    # 输出: 30AB 3099
print(unicodedata.decomposition('ñ'))     # 输出: 006E 0303

unicodedata.normalize(form, unistr) 将Unicode字符串转换为指定的规范化形式。支持四种规范化格式:

  • NFC (Canonical Decomposition, followed by Canonical Composition) - 标准分解后重组
  • NFKC (Compatibility Decomposition, followed by Canonical Composition) - 兼容分解后重组
  • NFD (Canonical Decomposition) - 标准分解
  • NFKD (Compatibility Decomposition) - 兼容分解
import unicodedata

# 文本规范化示例
title = "Klüft skräms inför på fédéral électoral große"

# 使用NFKD规范化并转换为ASCII
normalized = unicodedata.normalize('NFKD', title)
ascii_version = normalized.encode('ascii', 'ignore').decode('ascii')
print(ascii_version)  # 输出: Kluft skrams infor pa federal electoral groe

# 对比不同规范化形式
text = "café"  # 包含组合字符
print(f"原文: {text}")
print(f"NFC: {unicodedata.normalize('NFC', text)}")
print(f"NFD: {unicodedata.normalize('NFD', text)}")
print(f"NFKC: {unicodedata.normalize('NFKC', text)}")
print(f"NFKD: {unicodedata.normalize('NFKD', text)}")

版本信息

unicodedata.unidata_version 返回当前Unicode使用的数据库版本。

unicodedata.ucd_3_2_0 提供UCD 3.2的对象方式访问,以便兼容旧的IDNA应用程序。

import unicodedata

# 查询Unicode数据库版本
print(unicodedata.unidata_version)  # 输出: 13.0.0 (版本号会因Python版本而异)

# 访问旧版本UCD
print(unicodedata.ucd_3_2_0)  # 输出: <unicodedata.UCD object at 0x...>

💡 实际应用

文本清理和规范化

import unicodedata
import re

def clean_text(text):
    """清理和规范化文本"""
    # 规范化为NFKD形式
    normalized = unicodedata.normalize('NFKD', text)
    
    # 移除重音符号
    no_accents = ''.join(c for c in normalized 
                        if unicodedata.category(c) != 'Mn')
    
    # 转换为小写
    return no_accents.lower()

# 示例使用
text = "Café résumé naïve Zürich"
cleaned = clean_text(text)
print(f"原文: {text}")
print(f"清理后: {cleaned}")  # 输出: cafe resume naive zurich

字符分类统计

import unicodedata
from collections import Counter

def analyze_text(text):
    """分析文本中字符的类型分布"""
    categories = []
    for char in text:
        if char.strip():  # 忽略空白字符
            cat = unicodedata.category(char)
            categories.append(cat)
    
    return Counter(categories)

# 示例使用
text = "Hello, 世界! 123 @#$"
analysis = analyze_text(text)
for category, count in analysis.items():
    print(f"{category}: {count}")

数值字符处理

import unicodedata

def extract_numeric_values(text):
    """提取文本中所有数值字符的数值"""
    values = []
    for char in text:
        try:
            # 尝试获取数值
            value = unicodedata.numeric(char)
            values.append((char, value))
        except ValueError:
            # 非数值字符,跳过
            continue
    return values

# 示例使用
text = "第1章:五四运动,发生在1919年。数量:½ 罗马数字:Ⅴ"
numeric_chars = extract_numeric_values(text)
for char, value in numeric_chars:
    print(f"字符 '{char}' 的数值: {value}")

Unicode字符信息查询

import unicodedata

def get_char_info(char):
    """获取字符的详细信息"""
    try:
        info = {
            'character': char,
            'name': unicodedata.name(char),
            'category': unicodedata.category(char),
            'bidirectional': unicodedata.bidirectional(char),
            'combining': unicodedata.combining(char),
            'east_asian_width': unicodedata.east_asian_width(char),
            'mirrored': bool(unicodedata.mirrored(char)),
            'decomposition': unicodedata.decomposition(char) or 'None'
        }
        
        # 尝试获取数值
        try:
            info['numeric_value'] = unicodedata.numeric(char)
        except ValueError:
            info['numeric_value'] = 'N/A'
            
        return info
    except ValueError:
        return {'character': char, 'error': 'Character information not available'}

# 示例使用
for char in ['A', '中', '1', '€', '(']:
    info = get_char_info(char)
    print(f"\n字符 '{char}' 的信息:")
    for key, value in info.items():
        if key != 'character':
            print(f"  {key}: {value}")

文件名安全化

import unicodedata
import re

def safe_filename(filename):
    """将文件名转换为安全的ASCII文件名"""
    # 规范化文本
    normalized = unicodedata.normalize('NFKD', filename)
    
    # 移除重音符号和特殊字符
    ascii_name = normalized.encode('ascii', 'ignore').decode('ascii')
    
    # 替换不安全的字符
    safe_name = re.sub(r'[<>:"/\\|?*]', '_', ascii_name)
    
    # 移除多余的空格和下划线
    safe_name = re.sub(r'[_\s]+', '_', safe_name).strip('_')
    
    return safe_name

# 示例使用
filenames = [
    "Résumé (français).pdf",
    "データファイル.txt",
    "文档<版本1>.docx",
    "café/bar menu.xlsx"
]

for filename in filenames:
    safe = safe_filename(filename)
    print(f"原文件名: {filename}")
    print(f"安全文件名: {safe}\n")

⚠️ 注意事项

性能考虑

  • 大文本处理: 对于大量文本的规范化操作,考虑使用生成器或分块处理
  • 缓存结果: 频繁查询的字符信息可以缓存以提高性能
  • 选择合适的规范化形式: 不同的规范化形式有不同的性能特征

编码问题

  • 字符串类型: 确保输入是Unicode字符串,避免字节字符串导致的错误
  • 单字符限制: 大多数函数只接受单个字符作为参数
  • 异常处理: 合理处理KeyError、ValueError等异常

版本差异

  • Unicode版本: 不同Python版本使用的Unicode数据库版本可能不同
  • 字符支持: 较新的字符可能在旧版本中不被支持
  • 兼容性: 使用ucd_3_2_0进行向后兼容时要注意功能限制

国际化考虑

  • 语言特性: 不同语言的字符处理规则可能不同
  • 文化差异: 文本排序和比较需要考虑locale设置
  • 字体支持: 确保目标环境支持相应的字符显示

🔗 相关内容

📚 扩展阅读

🏷️ 标签

Unicode 字符处理 文本处理 字符编码 国际化 字符分类


最后更新: 2024-01-15
作者: Python文档团队
版本: 1.0

作者: Python文档团队

版本: 1.0

讨论与反馈

欢迎在下方留言讨论,分享你的学习心得或提出问题。评论基于GitHub Issues,需要GitHub账号。