飞翔飞翔
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
  • 学习路径
  • 第1章 认识Python

    • Python 历史与特点
    • Python 2 与 Python 3 的核心差异
    • 安装与运行 Python 2.7.18
    • 编码规范 PEP 8
  • 第2章 基础语法

    • 变量与对象
    • 数字类型
    • 字符串 str
    • Unicode 字符串
    • 运算符
    • 空值 None
  • 第3章 流程控制

    • if 条件语句
    • if-else 条件语句
    • if-elif-else 多分支
    • 条件表达式(三元运算符)
    • while 循环
    • for 循环
    • range 与 xrange
    • 循环控制:break、continue、pass
    • 循环 else 子句
  • 第4章 数据结构

    • 列表基础
    • 列表方法
    • 列表推导式
    • 元组
    • 字典基础
    • 字典方法
    • 字典循环技巧
    • 集合
    • 序列解包
    • 序列比较
  • 第5章 函数

    • 定义函数
    • 参数传递机制
    • 默认参数
    • 关键字参数
    • 可变参数
    • Lambda 表达式
    • 文档字符串
    • 函数对象
  • 第6章 模块与包

    • import 导入
    • 模块搜索路径
    • name 与主程序
    • 编译文件 .pyc 与 .pyo
    • 包结构
    • dir() 函数
  • 第7章 文件与IO

    • 打开与关闭文件
    • 文件读写方法
    • with 上下文管理器
    • 格式化输出:% 操作符
    • 格式化输出:str.format()
    • JSON 序列化
  • 第8章 面向对象

    • 类定义与实例化
    • init 构造方法
    • 类变量与实例变量
    • 方法调用与 self
    • 继承基础
    • 多重继承
    • 新式类与旧式类
    • 私有变量与名称改写
    • 属性装饰器 property
    • 类方法与静态方法
    • 魔术方法
    • 空类与数据记录
  • 第9章 异常处理

    • 异常类型
    • try-except
    • try-except-else-finally
    • 抛出异常 raise
    • 自定义异常
    • with 语句与上下文管理器
  • 第10章 迭代器与生成器

    • 迭代器协议
    • 生成器函数
    • 生成器表达式
    • itertools模块
  • 第11章 标准库精要

    • os模块
    • sys模块
    • datetime模块
    • re模块
    • json模块
    • collections模块
    • math与random模块
    • urllib2与网络请求
    • subprocess与命令执行
    • threading与并发
    • unittest与测试
    • 虚拟环境与包管理
  • 第12章 工程实践

    • 调试技巧
    • 性能分析
    • 文档与注释
    • 下一步学习

Unicode 字符串

Python 2 的字符串系统最大的设计缺陷,是同时存在两种字符串类型:str(字节串)和 unicode(Unicode 字符序列)。这种分裂导致无数编码错误,也是 Python 3 彻底重构字符串系统的核心原因。

为什么需要 unicode 类型

计算机最初只处理 ASCII 字符(128 个),用一个字节表示。但随着互联网全球化,中文、日文、阿拉伯文等文字需要被表示。Unicode 标准应运而生:它为世界上几乎所有字符分配了唯一的数字编号(码点,code point),范围从 U+0000 到 U+10FFFF。

unicode 类型在 Python 2 中存储的就是这些码点序列,而非字节。它屏蔽了底层编码细节,让程序员可以像处理 ASCII 一样处理任何文字:

u = u"你好,世界"
print type(u)       # <type 'unicode'>
print len(u)        # 6,6 个字符
print u[0]          # u'你'

对比 str 的混乱行为:

s = "你好,世界"    # 如果文件编码是 UTF-8,这实际上是 18 个字节
print type(s)       # <type 'str'>
print len(s)        # 18(UTF-8 编码下每个中文字符占 3 字节)
print s[0]          # '\xe4',一个字节,不是字符!

str 的 len() 返回字节数,unicode 的 len() 返回字符数。这是两者最本质的区别。

创建 unicode 对象

u1 = u"hello"           # 前缀 u 或 U
u2 = u'中文'
u3 = u"""多行
Unicode"""

# 用 unicode() 构造函数
u4 = unicode("hello")

# 从码点创建
u5 = unichr(20320)      # u'你',unichr 返回对应码点的 unicode 字符
print ord(u'你')        # 20320,返回码点值

unichr() 是 chr() 的 Unicode 版本。chr(65) 返回 'A'(str),unichr(65) 返回 u'A'(unicode)。

编码与解码

unicode 和 str 之间的转换必须显式进行,使用 encode(编码)和 decode(解码):

u = u"你好"

# unicode → str:编码(encode)
s = u.encode('utf-8')
print type(s)       # <type 'str'>
print repr(s)       # '\xe4\xbd\xa0\xe5\xa5\xbd'

# str → unicode:解码(decode)
u2 = s.decode('utf-8')
print type(u2)      # <type 'unicode'>
print u2            # 你好

编码是把字符序列变成字节序列,解码是把字节序列还原成字符序列。这个过程必须指定编码方式(如 UTF-8、GBK、ISO-8859-1),因为同样的字节序列在不同编码下代表完全不同的字符。

常见编码

编码特点适用场景
UTF-8变长编码,ASCII 字符 1 字节,中文 3 字节互联网标准,强烈推荐
UTF-16固定 2 或 4 字节Windows 内部使用
GBK中文 2 字节,兼容 ASCII中国大陆旧系统
ASCII仅 128 个字符纯英文文本
ISO-8859-1西欧语言,1 字节西欧旧系统

隐式转换的陷阱

Python 2 允许 str 和 unicode 在某些情况下自动转换,但这往往是 bug 的根源:

s = "hello"         # str
u = u"world"        # unicode
print s + u         # u"helloworld" —— str 被自动解码为 unicode

自动转换时,Python 会使用 sys.getdefaultencoding() 返回的编码(通常是 'ascii')来解码 str。如果 str 包含非 ASCII 字节,就会抛出 UnicodeDecodeError:

s = "你好"          # str,UTF-8 编码的字节
u = u"世界"         # unicode
print s + u         # UnicodeDecodeError: ascii codec can't decode...

错误信息中的 ascii codec 就是默认编码。解决方法是显式指定编码:

s = "你好"
u = u"世界"
print s.decode('utf-8') + u   # u"你好世界"

文件头编码声明

为了让 Python 2 正确解析源文件中的非 ASCII 字符,必须在文件第一行或第二行声明编码:

# -*- coding: utf-8 -*-
# 或者
# coding=utf-8

没有这行声明,Python 2 默认使用 ASCII 编码,遇到中文字符会抛出 SyntaxError: Non-ASCII character。

文件读写中的 Unicode

用 open() 读取文件时,返回的是 str(字节)。如果需要 unicode,必须手动解码:

# 读取文件
with open('data.txt', 'r') as f:
    s = f.read()            # str(字节)
    u = s.decode('utf-8')   # unicode

# 写入文件
u = u"你好"
with open('output.txt', 'w') as f:
    f.write(u.encode('utf-8'))   # 必须编码为字节才能写入

Python 2 的 codecs 模块提供了更便捷的 Unicode 文件操作:

import codecs

# 自动解码为 unicode
with codecs.open('data.txt', 'r', 'utf-8') as f:
    u = f.read()            # 直接返回 unicode

# 自动编码为字节
with codecs.open('output.txt', 'w', 'utf-8') as f:
    f.write(u"你好")        # 自动编码后写入

最佳实践

  1. 内部全部使用 unicode:程序内部处理文本时,始终使用 unicode 类型。
  2. 边界处编码/解码:在输入边界(文件、网络、数据库)将字节解码为 unicode;在输出边界将 unicode 编码为字节。
  3. 统一使用 UTF-8:UTF-8 是互联网的事实标准,能表示所有 Unicode 字符,且与 ASCII 兼容。
  4. 永远不要混用 str 和 unicode:相加、比较、格式化时,确保两者类型一致。

Python 3 彻底解决了这些问题:str 就是 Unicode,bytes 是独立的字节类型,两者不能隐式混用。理解 Python 2 的 unicode 机制,是理解 Python 3 字符串设计的最佳途径。

上一页
字符串 str
下一页
运算符