- Python 3 (推荐):
len()函数默认返回的是 Unicode 码位 的数量,对于绝大多数情况,这就是你想要的“字符”数量。 - Python 2 (已过时):
len()返回的是 字节 数量,这通常不是我们想要的。 - 复杂字符: 某些 Unicode 字符(如表情符号、某些特殊符号)可能由多个码位组成(家庭” emoji 👨👩👧👦 是由多个码位组合而成的)。
len()会将其视为多个独立的码位。 - 获取“字形簇”数量: 如果你需要计算用户感知的“字符”数量(即字形簇),需要使用第三方库
unicodedata。
Python 3 中的情况 (推荐)
在 Python 3 中,字符串是 Unicode 字符串,这是默认行为。len() 函数返回的是字符串中 Unicode 码位 的数量。

一个码位通常对应一个字符,但也不总是如此。
示例 1: 基本情况(ASCII 和常见汉字)
# ASCII 字符
s1 = "hello"
print(f"'{s1}' 的长度是: {len(s1)}") # 输出: 5
# 常见汉字
s2 = "你好,世界"
print(f"'{s2}' 的长度是: {len(s2)}") # 输出: 6
这里,“你”、“好”、“世”等每个汉字都是一个独立的码位,len() 正确地返回了 6。
示例 2: 复杂字符(代理对和组合字符)
这是 len() 行为的关键点。
情况 A: 代理对

某些字符(如 Emoji)在 BMP(基本多语言平面)之外,需要用两个 16 位的码位(一个“代理对”)来表示。
# 一个普通的 smiley emoji (在 BMP 内)
s_emoji_simple = "😊"
print(f"'{s_emoji_simple}' 的长度是: {len(s_emoji_simple)}") # 输出: 1
# 一个需要代理对的 emoji ("PILE OF POO" - 💩)
s_emoji_complex = "💩"
print(f"'{s_emoji_complex}' 的长度是: {len(s_emoji_complex)}") # 输出: 1
在 Python 3 中,即使内部存储可能需要多个字节,len() 仍然返回 1,因为它被正确地视为一个单一的码位。
情况 B: 组合字符
这是最容易出现“意外”的地方,一个完整的字符可能由一个基础字符和一个或多个组合标记(变音符号、上标等)组成。

# 'é' 可以由一个码位 U+00E7 直接表示
s_precomposed = "é"
print(f"'{s_precomposed}' (预组合) 的长度是: {len(s_precomposed)}") # 输出: 1
# 'é' 也可以由 'e' + 组合重音符号 '´' 组成
s_decomposed = "e\u0301" # \u0301 是组合重音符号
print(f"'{s_decomposed}' (分解形式) 的长度是: {len(s_decomposed)}") # 输出: 2
尽管这两个字符串在视觉上完全一样,但 len() 的结果不同,第一个是 1 个码位,第二个是 2 个码位('e' 和一个组合符号)。
Python 2 中的情况 (已过时)
在 Python 2 中,情况要复杂得多,字符串默认是字节串,而不是 Unicode 字符串。
- 对于字节串 (
str):len()返回字节数。 - 对于 Unicode 字符串 (
unicode):len()返回码位数。
强烈建议不要再使用 Python 2 进行新项目开发。
示例:
# -*- coding: utf-8 -*-
# Python 2 默认字节串
s_str = "你好"
print(f"字节串 '{s_str}' 的长度是: {len(s_str)}") # 输出: 6 (假设是 UTF-8 编码,每个汉字3字节)
# Python 2 Unicode 字符串
s_unicode = u"你好"
print(f"Unicode 字符串 '{s_unicode}' 的长度是: {len(s_unicode)}") # 输出: 2
如果你在 Python 2 中处理非英文字符,必须时刻记得你的变量是 str 还是 unicode,并正确地进行编码和解码,否则 len() 的结果会非常混乱。
如何获取“字形簇”的数量?
如上所述,len() 无法正确处理分解形式的组合字符,如果你需要计算用户感知的“字符”数量(即字形簇),你需要使用 unicodedata 模块进行 规范化。
规范化 是将 Unicode 字符串转换成一个标准形式的过程,对于我们的目的,最常用的是 NFC (Normalization Form C) 和 NFD (Normalization Form D)。
- NFC (Normalization Form C): 将字符转换为“预组合”形式,将 "e\u0301" 转换为 "é"。
- NFD (Normalization Form D): 将字符转换为“分解”形式,将 "é" 转换为 "e\u0301"。
策略:将字符串先规范化为 NFC 形式,然后再计算长度,这样可以确保大多数组合字符都被视为一个单一的码位。
示例:
import unicodedata
# 分解形式的 'é'
s_decomposed = "e\u0301"
print(f"分解前 '{s_decomposed}' 的长度是: {len(s_decomposed)}") # 输出: 2
# 将其规范化为 NFC (预组合) 形式
s_normalized = unicodedata.normalize('NFC', s_decomposed)
print(f"规范化后 '{s_normalized}' 的长度是: {len(s_normalized)}") # 输出: 1
一个更健壮的函数来获取“用户感知的字符数”:
import unicodedata
def get_grapheme_count(s):
"""
获取字符串中字形簇(用户感知的字符)的数量。
这是一个近似方法,对于所有情况(如某些 Emoji 组合)可能不完全准确,
但对于大多数情况已经足够。
"""
# 规范化为 NFC 形式,将组合字符合并
normalized_s = unicodedata.normalize('NFC', s)
return len(normalized_s)
# 测试
s1 = "hello"
s2 = "你好"
s3 = "e\u0301" # 分解的 é
s4 = "café" # 预组合的 é
s5 = "👨👩👧👦" # 家庭 emoji (由多个码位组合而成)
print(f"'{s1}': {get_grapheme_count(s1)}") # 输出: 5
print(f"'{s2}': {get_grapheme_count(s2)}") # 输出: 6
print(f"'{s3}': {get_grapheme_count(s3)}") # 输出: 1 (正确)
print(f"'{s4}': {get_grapheme_count(s4)}") # 输出: 4 (正确)
print(f"'{s5}': {get_grapheme_count(s5)}") # 输出: 1 (正确,因为它们被视为一个整体)
注意: 对于像 "👨👩👧👦" 这样的复杂 Emoji 组合,unicodedata.normalize 可能无法将其合并成一个码位,len() 仍然可能返回大于 1 的值,要完美处理这类情况,需要更专业的库,如 grapheme 库 (pip install grapheme)。
总结与实践建议
| 需求 | Python 3 推荐方法 | 说明 |
|---|---|---|
| 获取码位数量 | len(my_string) |
最常用,对于绝大多数现代应用,这就是正确的“长度”。 |
| 获取字节长度 | len(my_string.encode('utf-8')) |
当你需要将字符串存入数据库或通过网络传输时,需要知道它占用的字节数。 |
| 获取字形簇数量 | len(unicodedata.normalize('NFC', my_string)) |
当你需要计算用户看到的字符数时(如截断文本显示),对于 Emoji 组合,可能需要 grapheme 库。 |
最佳实践:
- 始终使用 Python 3,它从设计上就更好地支持了 Unicode。
- 在处理文本长度时,首先考虑
len(),它能满足 99% 的需求。 - 只有在遇到明显的组合字符问题(
len("e\u0301")返回 2 但你期望 1)时,才考虑使用unicodedata.normalize()。 - 如果你正在开发一个需要精确显示字符数量的应用(如社交媒体的字数限制),并且需要完美处理所有 Emoji,请考虑使用
grapheme这样的专业库。
