Skip to content

Python 字符串与编码:从"乱码之谜"到游刃有余

引言:为什么你的中文会变成"锟斤拷"?

你有没有遇到过这样的场景:打开一个文件,原本应该是"你好世界",却显示成了"浣犲ソ涓栫晫"或者"锟斤拷锟斤拷"?或者在网上复制一段文字,粘贴到另一个地方就变成了问号"???"?

这背后就是字符编码在作祟。今天,我们就来彻底搞懂这个让无数初学者头疼的问题,并学会如何在 Python 中优雅地处理字符串。


Python 中的字符串:str 与 bytes 的"双胞胎"

6.1 Python 3 的字符串设计

Python 3 做了一个重要决定:字符串(str)在内存中统一使用 Unicode

python
>>> print('包含中文的str')
包含中文的str

6.2 ord() 和 chr():字符与数字的桥梁

Python 提供了两个内置函数,用于查看字符的"身份证"(编码):

python
>>> ord('A')      # 查看字符的 Unicode 编码
65
>>> ord('中')     # 中文字符也有编码
20013

>>> chr(66)       # 通过编码找字符
'B'
>>> chr(25991)
'文'

生活化理解:ord() 像查字典(字 → 页码),chr() 像翻字典(页码 → 字)。

6.3 十六进制表示法

你还可以用十六进制直接写字符:

python
>>> '\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(编码)

把文本"打包"成二进制数据:

python
>>> '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(解码)

把二进制数据"拆包"成文本:

python
>>> b'ABC'.decode('ascii')
'ABC'

>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'

如果数据损坏或不匹配,会报错:

python
>>> 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():数"字"还是数"字节"?

python
>>> 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 文件开头加上:

python
#!/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 语言风格)

python
>>> '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

高级用法

python
>>> '%2d-%02d' % (3, 1)      # %2d:占2位;%02d:占2位,不足补0
' 3-01'

>>> '%.2f' % 3.1415926       # 保留2位小数
'3.14'

转义:想输出 % 本身,用 %%

python
>>> '增长率: %d%%' % 7
'增长率: 7%'

8.3 第二种:format() 方法

python
>>> 'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
'Hello, 小明, 成绩提升了 17.1%'

{0}{1} 是位置索引,{1:.1f} 表示第 2 个参数保留 1 位小数。

8.4 第三种:f-string(推荐!)

Python 3.6+ 引入的最简洁方式:

python
>>> 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 混用

python
# ❌ 错误
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 误区二:编码不一致

python
# ❌ 危险:用 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() 的困惑

python
# 你以为长度是 2,实际是 6
print(len('中文'))              # 2(字符数)
print(len('中文'.encode()))     # 6(UTF-8 字节数)

9.4 最佳实践清单

  1. 永远使用 UTF-8:除非有特殊需求(如对接老系统)
  2. 明确区分 str 和 bytes:网络/文件操作用 bytes,内存处理用 str
  3. 文件操作指定编码
    python
    with open('file.txt', 'r', encoding='utf-8') as f:
        content = f.read()
  4. 优先使用 f-string:Python 3.6+ 最简洁、最快
  5. 处理用户输入时防御性编程
    python
    try:
        text = data.decode('utf-8')
    except UnicodeDecodeError:
        text = data.decode('utf-8', errors='replace')

十、动手练习

题目:小明的成绩从去年的 72 分提升到了今年的 85 分,计算提升的百分点,并用 xx.x% 格式输出(保留 1 位小数)。

python
s1 = 72
s2 = 85
r = (s2 - s1) / s1 * 100
print(f'{r:.1f}%')    # 输出:18.1%

总结

概念核心要点
ASCII1 字节,128 字符,英文专用
GB23122 字节,中文专用
Unicode统一编码,2-4 字节,内存中使用
UTF-8可变长(1-4 字节),存储/传输使用
strPython 文本类型,内存中 Unicode
bytesPython 二进制类型,存储/传输用
encode()str → bytes
decode()bytes → str
f-string最推荐的字符串格式化方式

记住:在 Python 中,内存用 Unicode(str),传输存储用 UTF-8(bytes),转换时永远明确指定编码