2.3 字符串与格式化


2.3 字符串与格式化

本节摘要:str 是不可变序列,也是 Python 里出现频率最高的类型。本节讲它的不可变设计动机(哈希、共享、驻留)、切片与常用方法、f-string 为何成为格式化首选,以及 str 与 bytes 的分工——一句话真相:str 是文本,bytes 是字节,编码是两者之间的桥。

为什么字符串必须不可变

第 2.1 节说过 tuple 不可变的红利,str 把同样的逻辑推到极致。设想如果 str 可变:把 "hello" 交给一个函数,函数改了它的第一个字符,全世界所有引用 "hello" 的地方同时遭殃——因为驻留机制下它们共享同一个对象。不可变让字符串可以放心地作为 dict 键、放心地全局共享、放心地并发读取。这也是所有主流语言(Java、C#、Go)不约而同把字符串做成不可变的根本原因。

实证一下驻留与共享:

>>> a = "hello" >>> b = "hello" >>> a is b # 标识符样式的字面量被驻留共享 True >>> c = "hello world!" >>> d = "hello world!" >>> c is d # 含空格感叹号,REPL 中各自建对象(CPython 版本行为有差异) False >>> c == d # 值比较永远可靠 True

再次强调 1.2 节的结论:is 只用来判 None,字符串比较一律 ==

序列本性:下标、切片与遍历

str 完整实现了序列协议,第 2.1 节的下标与切片规则原样适用:

>>> s = "Pythonista" >>> s[0], s[-1] ('P', 'a') >>> s[2:5] 'tho' >>> s[::-1] # 反转的惯用法 'atsinohyP' >>> len(s), "thon" in s (10, True)

但注意:所有"修改"方法都返回新对象,原地无感:

>>> s.upper() 'PYTHONISTA' >>> s # 原串纹丝不动 'Pythonista'

高频方法按用途归类记忆,比背列表有效:

  • 查找判定find(找不到返回 -1)、index(找不到抛错)、startswithendswithin
  • 切分拼合split / rsplit / splitlines / join
  • 整理修饰strip / lstrip / rstripreplacezfill
  • 判定类别isdigitisalphaisspace(解析脏数据时的主力)。

其中 join 值得单独点名——拼接大量字符串时它是唯一的正确姿势:

>>> parts = ["id", "name", "email"] >>> ",".join(parts) 'id,name,email'

join 一次算总长、一次分配;而循环里 s += part 每步都可能造新串(现代 CPython 有原地优化,但那是实现细节,跨实现不可依赖)。两者的差距在万级拼接时可达百倍。

格式化演进与 f-string

Python 的格式化历史足以当一面镜子:% 格式(C 时代遗产)→ str.format(2.6)→ f-string(3.6)。前两种仍能见到,但新代码没有理由不用 f-string:

>>> name, score, ratio = "Ann", 92.4567, 0.873 >>> f"{name} 得分 {score:.1f},通过率 {ratio:.1%}" 'Ann 得分 92.5,通过率 87.3%' >>> f"{name=}" # 调试神器:连名字带值一起输出 "name='Ann'" >>> f"[{score:>10.2f}]" # 右对齐宽度 10 '[ 92.46]' >>> f"[{name:*^12}]" # 居中填充 '****Ann*****'

f-string 的本质是编译期把表达式直接编译进字节码,运行时求值一次完成,比 format 少一层方法调用与解析。用 dis 可以看到 FORMAT_VALUE 指令——它不是字符串替换魔法,是语言级语法。这也解释了为什么 f-string 是三者中最快的。

格式说明符的完整语法是一条迷你语言([[fill]align][sign][width][,][.precision][type]),日常记住三件套够用:.2f 定点、.1% 百分比、>10 / <10 / ^10 对齐。复杂数字格式(千分位、进制)查表即可:

>>> f"{1234567:,}" # 千分位 '1,234,567' >>> f"{255:#x} {255:b}" # 十六进制与二进制 '0xff 11111111'

(提醒一句:f-string 里可以写任意表达式,但别塞复杂逻辑——先算好再格式化,可读性天差地别。)

str 与 bytes:一句话真相

Python 3 把文本与字节彻底分开:str 是 Unicode 文本的序列,bytes 是原始字节的序列;两者不能隐式互转,只能显式编码解码:

>>> text = "中文" >>> blob = text.encode("utf-8") >>> blob b'\xe4\xb8\xad\xe6\x96\x87' >>> blob.decode("utf-8") '中文' >>> text + blob TypeError: can only concatenate str (not "bytes") to str

错误信息本身就在教育你:这是两个世界。乱码的统一解释是:编码与解码用了不同的码表。Windows 下读文件常见 UnicodeDecodeError: 'gbk' codec can't decode ...——因为默认编码随平台走,而文件是 UTF-8。解法是永远显式声明:

with open("data.txt", encoding="utf-8") as f: # 第 6 章详讲 content = f.read()

判断该用哪个:来自网络、文件、加密接口的"生数据"用 bytes;要显示、搜索、切片的"文本"用 str。len 的语义也随之分裂——len("中") 是 1(字符),len("中".encode()) 是 3(UTF-8 字节)。

模板与安全提示

处理用户提供的格式模板时要小心:str.format 能访问对象属性,恶意的格式串可能泄露数据(如 {0.__class__} 之类探索链)。规则:格式串必须是开发者可控的常量;需要用户模板时用 string.Template(只支持 $name 占位,无法穿透对象)。

⚠️ 常见坑汇总:"1" + 1 直接 TypeError,混用前先转换;"abc" < "abd" 是按码点比较,日期字符串必须补零(09 而不是 9)才能正确排序;split() 无参调用按任意空白切分并去空串,与 split(" ") 语义不同。

💡 关键直觉:把 str 想成"刻好的石碑"——想改内容只能重刻一块(新对象)。这个模型让你对每次"修改"操作的成本心里有数。

本节要点回顾

  • 不可变的动机:安全共享、驻留缓存、可作字典键;一切"修改"方法都返回新串。
  • join 是拼接正解:循环 += 是反模式,万级拼接差百倍。
  • f-string 三优势:编译进字节码、最快、name= 调试语法;格式串必须开发者可控。
  • str/bytes 分工:文本与字节两个世界,显式 encode/decode,显式指定 encoding。
  • len 语义分裂:字符数与字节数是两回事,多字节文本切片要当心。
  • 比较与排序:字符串按码点序,日期等"数值文本"必须补零。

第 3 章进入控制流:for 循环其实不是数数,而是在向容器索要迭代器——五大容器在"迭代协议"上会师。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U