字符串和编码

大家有没有遇到过复制粘贴代码出乱码、打开老中文txt全是方框的崩溃时刻?90%的这类问题,源头都是没搞懂「字符串和编码」!今天就从底层逻辑到Python实操,一口气讲明白👇


1. 字符编码基础

1.1 为什么要有编码?

先想清楚计算机的本质:它是只认二进制0和1的机器!哪怕是我们随手敲的“Hi 中文😊”,也得先转成一串数字(再转成二进制),才能在内存、磁盘、网络里跑。

而计算机最早诞生在美国,设计时用8个比特(bit,最小存储单位)凑成1个字节(byte,最常用的可寻址单位)——单字节最多能表示0-255共256个数字,完全够英文日常用。

1.2 ASCII:第一个通用但“排外”的标准

1967年发布的ASCII(美国信息交换标准代码),直接解决了“英文转数字”的问题:

  • 用0-127这128个数字,对应英文字母大小写、阿拉伯数字、标点符号、回车换行这些控制字符
  • 比如大写A=65,小写z=122,空格=32

但它的局限性也很明显:完全没考虑中文、日文、韩文这些非拉丁语系的文字

1.3 多语言混战:乱码的根源

为了让自己的语言能被计算机显示,各国开始自己搞编码标准:

  • 中国用GB2312(简化字,2字节存一个字),后来升级到GBK(兼容GB2312,还加了繁体字)、GB18030
  • 日本用Shift_JIS
  • 韩国用Euc-kr

麻烦的是:不同标准的同一段数字,对应完全不同的文字!比如同样的二进制存起来,在GBK里是“你好”,在Shift_JIS里可能就变成乱码了——这就是我们常说的“乱码困境”。

1.4 Unicode:一统江湖的“通用字符集”

直到1991年Unicode联盟发布Unicode标准,这个问题才算找到终极解决方案

  • 它给全球每一个文字、符号(甚至表情😊)都分配了唯一的“身份证号”,叫码点(Code Point)
  • 最常用的是UTF-16(日常文字2字节,生僻字/表情4字节),现代操作系统、编程语言的底层基本都用Unicode或其变种存内存

1.5 UTF-8:存储/传输的“性价比之王”

但UTF-16有个小缺点:哪怕是ASCII里的单个英文字母,也要占2字节,对于英文为主的文本,空间浪费了一半!

于是UTF-8(Unicode Transformation Format 8-bit)诞生了,它是Unicode的可变长编码实现,完美平衡了兼容性和存储效率:

  • 英文字母/ASCII字符:1字节(和ASCII完全一样,所以ASCII文件直接用UTF-8打开也没问题)
  • 常用汉字/日文假名:3字节
  • 生僻字符/表情:4-6字节

现在,全球99%的网页、软件、文件都默认用UTF-8


2. 实际工作中的编码流程

记住这个核心原则:「内存用Unicode,存储/传输转UTF-8」

  1. 打开文本、输入文字时:系统自动把UTF-8转成Unicode,存在内存里供你编辑
  2. 保存文件、发送消息时:再把Unicode转回UTF-8,存到磁盘或通过网络传输出去

比如网页里的<meta charset="UTF-8">,就是告诉浏览器:“请用UTF-8来解码这段内容哦!”


3. Python字符串处理(Python 3专属)

Python 3对字符串和编码做了彻底的优化:默认字符串就是Unicode,完全解决了Python 2里的str/unicode混用坑!

3.1 基础支持多语言

直接写就行,不用加任何前缀或转义:

print('包含中文、日文「こんにちは」、表情😊的Python字符串')

3.2 字符与Unicode码点的互转

  • ord(字符):获取单个字符的十进制Unicode码点
  • chr(码点):把十进制码点转回单个字符
ord('A')     # 65
ord('中')    # 20013
ord('😊')    # 128522
chr(66)      # 'B'
chr(25991)   # '文'
chr(128513)  # '😀'

3.3 Unicode码点的十六进制写法

如果不想用chr(),也可以直接在字符串里写\u加4位十六进制,或\U加8位十六进制(生僻字/表情用):

'\u4e2d\u6587'          # '中文'
'\U0001f600'            # '😀'

3.4 bytes类型:专门存二进制数据

网络传输、磁盘读写都不能直接传Unicode字符串,必须转成二进制流(bytes类型)

  • bytes用b前缀表示,每个元素是0-255的整数,显示时会把可打印的ASCII字符直接打出来,不可打印的用\x加两位十六进制表示
x = b'ABC'  # bytes类型,x[0] = 65,x[1] = 66,x[2] = 67

3.5 str与bytes的核心转换方法

  • str.encode(编码方式):Unicode字符串 → bytes(编码)
  • bytes.decode(编码方式):bytes → Unicode字符串(解码)

默认都用UTF-8,但最好显式指定,避免跨环境出问题:

# 编码
'ABC'.encode('ascii')       # b'ABC'(兼容ASCII)
'中文😊'.encode('utf-8')    # b'\xe4\xb8\xad\xe6\x96\x87\xf0\x9f\x98\x8a'

# 解码
b'ABC'.decode('ascii')                               # 'ABC'
b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')          # '中文'

3.6 解码错误的优雅处理

如果遇到部分损坏的二进制数据,直接用默认的strict模式会报错,可以用errors参数处理:

# 忽略错误的字节
b'\xe4\xb8\xad\xff\xe6\x96\x87'.decode('utf-8', errors='ignore')  # '中文'

# 用�替换错误的字节
b'\xe4\xb8\xad\xff\xe6\x96\x87'.decode('utf-8', errors='replace') # '中�文'

3.7 长度计算要注意区分类型

  • len(str):计算字符数
  • len(bytes):计算字节数
len('ABC')                # 3(3个字符)
len('中文😊')              # 3(3个字符)
len('中文😊'.encode('utf-8'))  # 3+3+4=10(字节数)

3.8 Python源代码的编码规范

如果你的Python文件里有中文、日文等非ASCII字符,必须做两件事

  1. 把文件保存为UTF-8格式(现代编辑器默认都是,但最好确认一下)
  2. 在文件最开头加编码声明(虽然Python 3默认会尝试用UTF-8,但显式声明更规范)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

print('这段中文在Python文件里没问题哦!')

4. Python字符串格式化(3种主流方式)

4.1 %格式化:传统但兼容性强

这是Python 1就有的方式,用%s(字符串)、%d(整数)、%f(浮点数)等占位符:

'Hello, %s!' % 'world'              # 'Hello, world!'
'Hi, %s, 你有 %d 元余额。' % ('小明', 1000000)  # 多参数用元组
'%2d-%02d' % (3, 1)                 # ' 3-01'(补空格/补零)
'%.2f' % 3.1415926                  # '3.14'(保留2位小数)
'增长率: %d %%' % 7                 # '增长率: 7 %'(输出%要用%%转义)

4.2 format()方法:Python 2.7/3.0引入

{}占位符,支持按位置、按名字传参,更灵活:

# 按位置传参
'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)  # 'Hello, 小明, 成绩提升了 17.1%'

# 按名字传参
'Hello, {name}, 成绩提升了 {rate:.1f}%'.format(name='小明', rate=17.125)

4.3 f-string:Python 3.6+推荐!(最简洁最快)

直接在字符串前加f,占位符里写变量名或表达式,还支持各种格式化:

name = '小明'
s1 = 72
s2 = 85
rate = (s2 - s1) / s1 * 100

f'Hello, {name}, 成绩提升了 {rate:.1f}%'  # 'Hello, 小明, 成绩提升了 18.1%'

5. 避坑最佳实践

  1. 默认全用UTF-8:不管是文本文件、代码保存、网络接口、数据库,能选UTF-8就选UTF-8
  2. 优先用f-string:Python 3.6+环境下,它是最简洁、可读性最高、性能最好的格式化方式
  3. 处理文件I/O必须显式指定编码:不要依赖系统默认编码(Windows默认GBK,Mac/Linux默认UTF-8,容易出乱码)
    # 读取文件
    with open('test.txt', 'r', encoding='utf-8') as f:
        content = f.read()
    
    # 写入文件
    with open('test.txt', 'w', encoding='utf-8') as f:
        f.write('中文内容')
  4. Python 3里千万别混用str和bytes:两者不能直接拼接、比较,必须显式转换

6. 小练习

用Python 3的f-string计算小明的成绩提升率:

s1 = 72  # 上次成绩
s2 = 85  # 本次成绩
rate = (s2 - s1) / s1 * 100
print(f'小明的成绩提升了 {rate:.1f}%')  # 输出:小明的成绩提升了 18.1%

7. 总结

  • 计算机只能认二进制,所以文本必须转成编码(数字→二进制)
  • Unicode是「通用字符身份证」,内存里都用它;UTF-8是「性价比传输编码」,存储/传输用它
  • Python 3的str默认是Unicode,完美解决多语言问题;bytes专门存二进制
  • 字符串格式化优先用Python 3.6+的f-string
  • 始终显式指定UTF-8,避免跨环境乱码!