查看: 296|回复: 0

Python ord()与chr()码点转换陷阱及安全处理

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
Python 中 ord() 和 chr() 负责在单个字符与 Unicode 码点之间互转,看似简单,实际涉及码点、编码字节和字素簇三层模型。原文强调的核心事实是:ord() 只接受长度为 1 的 str,或长度为 1 的 bytes/bytearray;chr() 只接受 0 到 0x10FFFF 的整数。理解这两个边界,比记住函数名更重要。

【先厘清码点、字节和字素簇】
Unicode 码点是字符的唯一整数编号,例如 'A' 是 65,'中' 是 20013,'😀' 是 128512。ord(c) 返回的是码点,不是 UTF-8 编码后的字节值。'中'.encode('utf-8') 得到三个字节,而 ord('中') 仍是 20013。Python 3 的 str 是码点序列,len() 返回码点数;UTF-8 的码元是字节,UTF-16 的码元是 16 位单元。emoji 和带音标字母还可能是多个码点组成的字素簇,ord() 无法直接处理整个字素簇。

【典型错误与原因】
第一类错误是 ord() 参数长度不为 1:
  1. ord('')
  2. # TypeError: ord() expected a character, but string of length 0 found
  3. ord('ab')
  4. # TypeError: ord() expected a character, but string of length 2 found
  5. ord(b'')
  6. # TypeError
复制代码
要获取整个字符串的码点,必须遍历:
  1. codes = [ord(c) for c in 'hello']
  2. print(codes)
  3. # [104, 101, 108, 108, 111]
复制代码
第二类错误是 chr() 参数越界或类型不对:
  1. chr(-1)
  2. # ValueError: chr() arg not in range(0x110000)
  3. chr(0x110000)
  4. # ValueError: chr() arg not in range(0x110000)
  5. chr('65')
  6. # TypeError
  7. chr(65.0)
  8. # TypeError
复制代码
Unicode 码点有效范围是 0 到 0x10FFFF。chr() 只接受整数,不接受字符串或浮点数。

第三类错误是代理码点导致编码失败。0xD800 到 0xDFFF 是 UTF-16 代理区,Python 允许 chr() 生成代理字符,但单独放入字符串后通常不能编码为 UTF-8:
  1. c = chr(0xD800)
  2. print(repr(c))
  3. c.encode('utf-8')
  4. # UnicodeEncodeError: surrogates not allowed
复制代码
除非在专门处理 UTF-16 代理对,否则应避免生成代理码点。

第四类错误是把 ord() 当成 encode()。ord('中') 返回 20013,是码点;要拿 UTF-8 字节值应使用 encode():
  1. print(ord('中'))
  2. # 20013
  3. print(list('中'.encode('utf-8')))
  4. # [228, 184, 173]
复制代码
ord() 与 encode() 的职责不同:前者做字符到码点,后者做字符到编码字节。

第五类错误是对 emoji 或组合字符直接调用 ord()。家庭 emoji 由多个码点组成,ord(emoji) 会因字符串长度不为 1 报 TypeError;ord(emoji[0]) 只能取第一个码点。组合字符如 e 加重音符号也是两个码点,需要遍历或先做 Unicode 规范化:
  1. import unicodedata
  2. s = 'e' + chr(0x0301)
  3. print(len(s))
  4. # 2
  5. normalized = unicodedata.normalize('NFC', s)
  6. print(len(normalized))
  7. print(ord(normalized))
  8. # 233
复制代码
另外,ord() 对长度为 1 的 bytes 或 bytearray 会返回 0 到 255 的字节值,例如 ord(b'A') 返回 65,ord(bytearray(b'A')) 也返回 65;但 ord(b'AB') 会报长度错误。这个行为容易与 str 的码点语义混淆,使用时应明确输入类型。

【可落地的安全写法】
对 chr() 做类型、范围和代理区检查,可以避免大部分运行时异常:
  1. def safe_chr(i):
  2.     if not isinstance(i, int):
  3.         raise TypeError('码点必须是整数')
  4.     if not (0 <= i <= 0x10FFFF):
  5.         raise ValueError(f'码点 {i} 超出 Unicode 范围')
  6.     if 0xD800 <= i <= 0xDFFF:
  7.         raise ValueError(f'码点 {i} 是代理码点,不能单独使用')
  8.     return chr(i)
复制代码
从码点列表还原字符串时,可用 join 配合 chr():
  1. codes = [72, 101, 108, 108, 111]
  2. s = ''.join(chr(c) for c in codes)
  3. print(s)
  4. # Hello
复制代码
生成字符范围也是常见用法,例如生成 A 到 Z:
  1. letters = [chr(i) for i in range(ord('A'), ord('Z') + 1)]
  2. print(letters)
复制代码
获取字符的十六进制码点表示,便于对照 Unicode 表:
  1. c = '中'
  2. print(hex(ord(c)))
  3. # 0x4e2d
  4. print(f'U+{ord(c):04X}')
  5. # U+4E2D
复制代码
需要查询字符身份时,可用 unicodedata:
  1. import unicodedata
  2. c = '中'
  3. print(unicodedata.name(c))
  4. print(unicodedata.category(c))
复制代码
处理用户输入时,应明确要求单个字符,并校验长度:
  1. def char_to_code(s):
  2.     if len(s) != 1:
  3.         raise ValueError('请输入单个字符')
  4.     return ord(s)
  5. def code_to_char(i):
  6.     try:
  7.         return chr(i)
  8.     except ValueError as e:
  9.         raise ValueError(f'无效码点: {i}') from e
复制代码

【调试与边界测试】
调试 Unicode 问题时,优先打印 repr(),确认字符真实表示;用 hex() 和 U+ 格式对照码点;用 len() 判断是单个码点还是多个码点;用 unicodedata.name() 确认字符身份。对 chr() 生成的字符尝试 encode('utf-8'),可以暴露代理码点问题。终端显示为方块或乱码不一定是 Python 错误,也可能是字体不支持,字符本身仍可能有效。测试中应覆盖 0、0x10FFFF、代理区以及多码点 emoji 等边界。

【最佳实践】
ord() 只处理单个码点,多字符字符串要遍历;chr() 参数必须是 0 到 0x10FFFF 的整数,并避开代理区;码点不是字节,不要用 ord() 代替 encode();处理 emoji 和组合字符时,注意字素簇与规范化;在输入校验、文档和测试中明确区分码点、字节和字素簇。只要牢记 ord() 与 chr() 只围绕单个码点工作,并在边界处主动校验,就能减少 TypeError、ValueError 和 UnicodeEncodeError 带来的排查成本。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-21 12:59 , Processed in 0.029280 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部