Python 字符串与 Unicode 处理
Python 中的 str 是不可变的 Unicode 文本,bytes 是二进制数据,可能表示编码后的文本。文本与字节之间的转换需要一种编码。
text = "café"
payload = text.encode("utf-8")
restored = payload.decode("utf-8")
核心原则:在程序内部始终使用 str 处理文本,仅在 I/O 边界(文件读写、网络传输、子进程通信)进行 encode 和 decode 操作。
序列操作
name = "Ada Lovelace"
first = name[:3]
last_character = name[-1]
contains_space = " " in name
注意:索引和切片基于 Unicode 码点(code points),而非人类感知的字素簇(grapheme clusters)。某些视觉上的单个字符(如带组合音标的字母、Emoji)可能由多个码点组成,直接切片可能导致字符截断或乱码。
字符串不可变,转换操作返回字符串结果,不修改原字符串;结果不保证一定是身份不同的新对象:
normalized = raw.strip().casefold()
words = normalized.split()
line = ", ".join(words)
性能提示:在循环中频繁使用 += 拼接字符串可能产生不必要的临时对象,增加构建成本。如果构建成本敏感,建议先收集片段到列表中,最后使用 join 一次性拼接。
格式化
对于局部变量插值和简单格式控制,优先使用 f-string,代码更直观:
message = f"{user}: {total:,.2f} CAD"
安全警告:格式化 转义。f-string 仅负责字符串拼接,不具备任何安全过滤能力。处理 SQL 查询、HTML 内容、Shell 命令、正则表达式或 URL 时,必须使用各自领域专用的转义或参数化 API,切勿依赖 f-string 来防止注入攻击。
Unicode 相等性
视觉相似的文本可能对应不同的码点序列(例如全角/半角、不同组合方式的音标)。
- 规范化:当业务逻辑要求严格的规范比较时,需先执行 Unicode 规范化(Normalization)。
- 大小写匹配:进行不区分大小写的匹配时,使用
casefold()而非lower()。casefold针对搜索场景优化,能处理更多语言的特殊大小写规则。 - 排序与本地化:涉及区域设置(Locale)的排序规则或人名处理,建议引入专门的领域库,标准库的默认行为通常无法满足复杂的国际化需求。
切片、清理与规范比较
对于字符串方法,索引从零开始,负索引从末尾计数。text[start:stop:step] 不包含 stop;切片边界越界时会裁剪,单个索引越界则抛出 IndexError。切片步长为零会抛出 ValueError。
assert "abc"[1:99] == "bc"
assert "abc"[::-1] == "cba"
assert "abc"[5:] == ""
assert " a b ".split() == ["a", "b"]
assert "a,,b".split(",") == ["a", "", "b"]
assert "abbaXba".strip("ab") == "X"
assert "report.txt".removesuffix(".txt") == "report"
strip(chars) 会从两端反复移除参数中的任意字符,并不是删除一个完整前缀或后缀。找不到子串时,find 返回 -1,index 抛出 ValueError;只需判断是否存在时用 in。join 要求元素为字符串,不会自动转换数字。
Unicode HOWTO区分码点相等与规范等价:
import unicodedata
composed = "é"
decomposed = "e\u0301"
assert composed != decomposed
assert (len(composed), len(decomposed)) == (1, 2)
assert unicodedata.normalize("NFC", decomposed) == composed
assert "Straße".casefold() == "strasse"
assert len("café".encode("utf-8")) == 5
NFC 不会合并所有外观相似的字符,也不负责忽略大小写。NFKC 等兼容规范化可能抹去业务需要保留的区别。无效 UTF-8 默认会触发 UnicodeDecodeError,忽略错误会悄悄丢失数据。二进制数据也可能根本不是文本。