在Python中处理字符串时,编码问题几乎是绕不开的话题。不少初学者分不清Unicode、UTF-8和字节串之间的关系,而chr()函数恰恰是理解这整套体系的一个绝佳切入点。它只做一件事:把一个整数转换成对应的Unicode字符。这个看似简单的操作,背后连接的是整个Unicode字符集的映射体系。

chr()函数的基本用法与参数规则
chr()是Python的内置函数,接收一个整数参数,返回该整数对应的Unicode字符。它的语法非常简单:
c = chr(65) print(c) # 输出 A c2 = chr(20013) print(c2) # 输出 中 c3 = chr(128512) print(c3) # 输出 😀
参数的有效范围是0到1114111,也就是十六进制的0x10FFFF,这正是Unicode编码空间的上限。如果传入超出范围的整数,Python会抛出ValueError异常:
try:
chr(1114112)
except ValueError as e:
print(e) # chr() arg not in range(0x110000)还有一个容易忽略的细节:如果传入的是负数,同样会触发ValueError,因为Unicode码点不存在负值。而如果传入的不是整数类型,比如一个字符串或者浮点数,则会触发TypeError。所以在编写需要动态调用chr()的代码时,最好先对参数做类型和范围校验,避免程序在运行时崩溃。
另外要注意,chr()的参数虽然通常用十进制表示,但也可以直接传入十六进制的字面量,例如chr(0x4E2D)和chr(20013)的结果完全相同,都是字符"中"。Unicode码点表中习惯用十六进制表示,所以在查阅字符编码资料时,用十六进制传参往往更直观。
chr()与ord()的互逆关系
ord()可以看作chr()的逆运算:前者把单个字符转换成码点整数,后者把码点整数还原成字符。两者配合使用,可以实现字符的各种数学运算。
s = "A"
# 先转成码点,加1,再转回字符
shifted = chr(ord(s) + 1)
print(shifted) # 输出 B
# 经典的凯撒密码:字母向后位移3位
def caesar(text, offset=3):
result = []
for ch in text:
if ch.isalpha():
base = ord('a') if ch.islower() else ord('A')
result.append(chr((ord(ch) - base + offset) % 26 + base))
else:
result.append(ch)
return ''.join(result)
print(caesar("Hello World")) # 输出 Khoor Zruog上面的凯撒密码示例展示了这对函数最典型的应用场景:把字符当作数字来处理。任何需要对字符做偏移、轮转、加密的操作,基本都离不开ord()和chr()的组合。判断一个字符是大写还是小写后,通过ord('A')或ord('a')作为基准值进行相对运算,是最常见的写法,它能保证字母在位移后仍在原有大小写区间内循环。
需要强调的是,ord()只能接收长度为1的字符串,传入空字符串或者多字符的字符串都会抛出TypeError。这一点和chr()要求整数参数是对称的,理解了这种对称性,就能明白它们操作的其实是同一个东西——Unicode码点。
chr()、encode与decode:分清字符与字节两个层面
很多编码混乱的根源在于没分清"字符"和"字节"这两个概念。chr()操作的是字符层面,它产出的结果是Python的str类型;而encode()和decode()操作的是字节层面,涉及的是bytes类型。
# chr 产出的是字符(str)
c = chr(20013)
print(type(c)) # <class 'str'>
# 字符编码成字节
b = c.encode('utf-8')
print(b) # b'\xe4\xb8\xad'
print(len(b)) # 3,UTF-8中一个汉字占3个字节
# 字节解码回字符
s = b.decode('utf-8')
print(s) # 中
# 从字节反推码点
print(hex(ord(s))) # 0x4e2d从上面的例子可以看出,码点20013经过UTF-8编码后变成了3个字节的序列。Unicode码点是字符的唯一编号,而UTF-8是把这个编号存储到内存或磁盘时的具体编码方案。同一个码点用UTF-8、GBK等不同方案编码,得到的字节序列是不一样的。chr()只知道码点,不关心编码方案,这是它和encode()的本质区别。
一个实用技巧:想查看某个汉字在某种编码下的字节形式,可以用chr(i).encode('gbk')这样的链式调用;反过来,手头有字节序列想确认它对应什么字符,则用b.decode('gbk')再配合ord()查看码点。在排查乱码问题时,这套组合拳非常有效。
利用chr()批量生成字符与遍历字符区间
chr()配合range()可以批量生成一段连续的字符,这在生成测试数据、构建字符映射表时很常用。
# 生成所有大写字母
upper = ''.join(chr(i) for i in range(65, 91))
print(upper) # ABCDEFGHIJKLMNOPQRSTUVWXYZ
# 生成数字字符和小写字母
digits = ''.join(chr(i) for i in range(48, 58))
print(digits) # 0123456789
# 遍历常用汉字区段(举例:U+4E00到U+4E09)
for i in range(0x4E00, 0x4E0A):
print(chr(i), end=' ')
# 输出: 一 丁 丂 七 丄 丅 丆 万 丂 丈常用的Unicode区间值得记住几个:48到57是数字0到9,65到90是大写字母A到Z,97到122是小写字母a到z,而中文常用汉字集中在19968(0x4E00)到40869(0x9FA5)这个区间。掌握了这些区间,就能用chr()快速判断字符类别,比如判断一个字符是不是汉字:
def is_chinese(ch):
return 0x4E00 <= ord(ch) <= 0x9FA5
print(is_chinese('中')) # True
print(is_chinese('a')) # False当然,Python内置的str.isalpha()、str.isdigit()等方法已经能覆盖大部分判断需求,但在需要更精细控制的场景下,比如只匹配某个特定区段的汉字,直接操作码点依然是最灵活的方式。
使用chr()时的常见坑点
第一个坑是代理区(Surrogate)问题。Unicode码点范围中的55296到57343(0xD800到0xDFFF)是UTF-16的代理区,这个区间的码点没有分配任何实际字符。虽然CPython的chr()在宽字符构建下能返回这些代理字符,但它们无法被编码成UTF-8,调用encode()时会直接报错。所以不要盲目地用range(0, 0x110000)去遍历所有字符并编码,最好跳过代理区。
第二个坑是控制字符。码点0到31对应的控制字符(如换行符10、回车符13)在打印时可能产生意外效果。如果你在批量生成字符后直接输出,遇到这些字符会导致输出错乱,建议打印前用repr()查看真实内容。
第三个坑是版本兼容性。chr()返回的具体字形取决于系统和字体对Unicode的支持程度,一些生僻字在缺少字体的环境下可能显示为方块,但这不代表转换出错,只是显示层面的问题。用ord()验证一下码点就能确认数据本身是正确的。
总结来说,chr()是Python中连接整数世界和字符世界的桥梁。理解了它和ord()、encode()、decode()的分工协作,就掌握了Python编码体系的核心脉络。无论是写加密算法、处理文本数据还是排查乱码问题,这几个函数的组合使用都能让你的工作事半功倍。
chr函数Python编码转换Unicode码点修改时间:2026-09-09 04:26:41