Python 字符串与编码:从"乱码之谜"到游刃有余
引言:为什么你的中文会变成"锟斤拷"?
你有没有遇到过这样的场景:打开一个文件,原本应该是"你好世界",却显示成了"浣犲ソ涓栫晫"或者"锟斤拷锟斤拷"?或者在网上复制一段文字,粘贴到另一个地方就变成了问号"???"?
这背后就是字符编码在作祟。今天,我们就来彻底搞懂这个让无数初学者头疼的问题,并学会如何在 Python 中优雅地处理字符串。
Python 中的字符串:str 与 bytes 的"双胞胎"
6.1 Python 3 的字符串设计
Python 3 做了一个重要决定:字符串(str)在内存中统一使用 Unicode。
>>> print('包含中文的str')
包含中文的str6.2 ord() 和 chr():字符与数字的桥梁
Python 提供了两个内置函数,用于查看字符的"身份证"(编码):
>>> ord('A') # 查看字符的 Unicode 编码
65
>>> ord('中') # 中文字符也有编码
20013
>>> chr(66) # 通过编码找字符
'B'
>>> chr(25991)
'文'生活化理解:ord() 像查字典(字 → 页码),chr() 像翻字典(页码 → 字)。
6.3 十六进制表示法
你还可以用十六进制直接写字符:
>>> '\u4e2d\u6587' # \u 后面跟 4 位十六进制
'中文'这在处理特殊字符时特别有用。
6.4 str vs bytes:文本与数据的"变身"
这是 Python 编码中最核心的概念:
| 类型 | 表示 | 用途 | 例子 |
|---|---|---|---|
| str | 文本 | 内存中处理、显示 | '中文' |
| bytes | 二进制数据 | 存储、网络传输 | b'\xe4\xb8\xad' |
关键区别:
'ABC'是 str,每个字符是"文字"b'ABC'是 bytes,每个"字符"是 1 个字节的数字
6.5 encode():str → bytes(编码)
把文本"打包"成二进制数据:
>>> 'ABC'.encode('ascii') # 英文用 ASCII
b'ABC'
>>> '中文'.encode('utf-8') # 中文用 UTF-8
b'\xe4\xb8\xad\xe6\x96\x87'
>>> '中文'.encode('ascii') # 报错!ASCII 装不下中文
UnicodeEncodeError: 'ascii' codec can't encode characters...生活化理解:encode() 就像把信件(文字)装进信封(二进制数据),准备寄出。
6.6 decode():bytes → str(解码)
把二进制数据"拆包"成文本:
>>> b'ABC'.decode('ascii')
'ABC'
>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'如果数据损坏或不匹配,会报错:
>>> b'\xe4\xb8\xad\xff'.decode('utf-8') # \xff 不是合法的 UTF-8
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff...
# 可以忽略错误字节
>>> b'\xe4\xb8\xad\xff'.decode('utf-8', errors='ignore')
'中'6.7 len():数"字"还是数"字节"?
>>> len('ABC') # str:数字符个数
3
>>> len('中文') # 2 个汉字
2
>>> len(b'ABC') # bytes:数字节数
3
>>> len('中文'.encode('utf-8')) # UTF-8 编码后 6 字节
6重要结论:1 个汉字 = 3 个 UTF-8 字节
七、实战:Python 源代码的编码声明
7.1 为什么需要声明编码?
Python 源代码本身也是文本文件。如果包含中文,必须告诉 Python 解释器用什么编码读取。
7.2 标准做法
在 .py 文件开头加上:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-- 第一行:告诉 Linux/Mac 系统"这是 Python 程序"(Windows 会忽略)
- 第二行:告诉 Python"请用 UTF-8 读取我"
7.3 常见误区
误区 1:只加声明,不保存为 UTF-8
- 声明只是"告诉"Python 怎么读,文件本身必须真的是 UTF-8 编码
误区 2:在 Windows 记事本中保存
- Windows 记事本默认用 ANSI/GBK 编码,容易出问题
- 推荐使用 VS Code、PyCharm、Sublime Text 等专业编辑器
八、字符串格式化:让输出更优雅
8.1 为什么需要格式化?
想象你要发 100 条短信:"亲爱的张三,您 1 月的话费是 50 元,余额是 20 元"。
如果手动改名字、月份、金额、余额,会累死。格式化就是模板替换。
8.2 第一种:% 格式化(C 语言风格)
>>> 'Hello, %s' % 'world'
'Hello, world'
>>> 'Hi, %s, you have $%d.' % ('Michael', 1000000)
'Hi, Michael, you have $1000000.'常用占位符:
| 占位符 | 含义 | 示例 |
|---|---|---|
| %s | 字符串(万能) | %s |
| %d | 整数 | %d |
| %f | 浮点数 | %.2f(保留2位小数) |
| %x | 十六进制 | %x |
高级用法:
>>> '%2d-%02d' % (3, 1) # %2d:占2位;%02d:占2位,不足补0
' 3-01'
>>> '%.2f' % 3.1415926 # 保留2位小数
'3.14'转义:想输出 % 本身,用 %%
>>> '增长率: %d%%' % 7
'增长率: 7%'8.3 第二种:format() 方法
>>> 'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
'Hello, 小明, 成绩提升了 17.1%'{0}、{1} 是位置索引,{1:.1f} 表示第 2 个参数保留 1 位小数。
8.4 第三种:f-string(推荐!)
Python 3.6+ 引入的最简洁方式:
>>> r = 2.5
>>> s = 3.14 * r ** 2
>>> print(f'半径为 {r} 的圆面积是 {s:.2f}')
半径为 2.5 的圆面积是 19.62优点:
- 直接在字符串里写变量名,直观
- 支持表达式:
{r * 2} - 支持格式化:
{s:.2f}
九、常见误区与最佳实践
9.1 误区一:str 和 bytes 混用
# ❌ 错误
data = '中文'
with open('file.txt', 'wb') as f:
f.write(data) # 需要 bytes,给了 str
# ✅ 正确
with open('file.txt', 'wb') as f:
f.write(data.encode('utf-8'))9.2 误区二:编码不一致
# ❌ 危险:用 GBK 编码,用 UTF-8 解码
text = '中文'
data = text.encode('gbk') # GBK 编码
result = data.decode('utf-8') # 用 UTF-8 解码 → 乱码或报错
# ✅ 正确:编码解码要一致
data = text.encode('utf-8')
result = data.decode('utf-8')9.3 误区三:len() 的困惑
# 你以为长度是 2,实际是 6
print(len('中文')) # 2(字符数)
print(len('中文'.encode())) # 6(UTF-8 字节数)9.4 最佳实践清单
- 永远使用 UTF-8:除非有特殊需求(如对接老系统)
- 明确区分 str 和 bytes:网络/文件操作用 bytes,内存处理用 str
- 文件操作指定编码:python
with open('file.txt', 'r', encoding='utf-8') as f: content = f.read() - 优先使用 f-string:Python 3.6+ 最简洁、最快
- 处理用户输入时防御性编程:python
try: text = data.decode('utf-8') except UnicodeDecodeError: text = data.decode('utf-8', errors='replace')
十、动手练习
题目:小明的成绩从去年的 72 分提升到了今年的 85 分,计算提升的百分点,并用 xx.x% 格式输出(保留 1 位小数)。
s1 = 72
s2 = 85
r = (s2 - s1) / s1 * 100
print(f'{r:.1f}%') # 输出:18.1%总结
| 概念 | 核心要点 |
|---|---|
| ASCII | 1 字节,128 字符,英文专用 |
| GB2312 | 2 字节,中文专用 |
| Unicode | 统一编码,2-4 字节,内存中使用 |
| UTF-8 | 可变长(1-4 字节),存储/传输使用 |
| str | Python 文本类型,内存中 Unicode |
| bytes | Python 二进制类型,存储/传输用 |
| encode() | str → bytes |
| decode() | bytes → str |
| f-string | 最推荐的字符串格式化方式 |
记住:在 Python 中,内存用 Unicode(str),传输存储用 UTF-8(bytes),转换时永远明确指定编码。