对于只用 Python 2 编程的程序员来说,字符串的话题可能会造成一些困惑。Python 3 中
只有一种能够保存文本信息的数据类型,就是 str(string,字符串)。它是不可变的序列,
保存的是 Unicode 码位(code point)。这是与 Python 2 的主要区别,Python 2 用 str 表示字
节字符串,这种类型现在在 Python 3 中用 bytes 对象来处理(但处理方式并不完全相同)。
Python 中的字符串是序列。基于这一事实,应该把字符串放在其他容器类型的一节去
介绍,但字符串与其他容器类型在细节上有一个很重要的差异。字符串可以保存的数据类
型有非常明确的限制,就是 Unicode 文本。
bytes 以及可变的 bytearray 与 str 不同,只能用字节作为序列值,即 0 <= x <
256 范围内的整数。一开始可能会有点糊涂,因为其打印结果与字符串非常相似:
print(bytes([102, 111, 111]))
b’foo’
对于 bytes 和 bytearray,在转换为另一种序列类型(例如 list 或 tuple)时可
以显示出其本来面目:
list(b’foo bar’)
[102, 111, 111, 32, 98, 97, 114]
tuple(b’foo bar’)
(102, 111, 111, 32, 98, 97, 114)
许多关于 Python 3 的争议都是关于打破字符串的向后兼容和 Unicode 的处理方式。从
Python 3.0 开始,所有没有前缀的字符串都是 Unicode。因此,所有用单引号(‘)、双引号
(“)或成组的 3 个引号(单引号或双引号)包围且没有前缀的值都表示 str 数据类型:
type(“some string”)
<class ‘str’>
在 Python 2 中,Unicode 需要有 u 前缀(例如 u"some string”)。从 Python 3.3 开
始,为保证向后兼容,仍然可以使用这个前缀,但它在 Python 3 中没有任何语法上的意义。
前面的一些例子中已经提到过字节,但为了保持前后一致,我们来明确介绍它的语法。
字节也被单引号、双引号或三引号包围,但必须有一个 b 或 B 前缀:
type(b"some bytes")
<class ‘bytes’>
注意,Python 语法中没有 bytearray 字面值。
最后同样重要的是,Unicode 字符串中包含无法用字节表示的“抽象”文本。因此,
如果 Unicode 字符串没有被编码为二进制数据的话,是无法保存在磁盘中或通过网络发送
的。将字符串对象编码为字节序列的方法有两种:
• 利用str.encode(encoding, errors)方法,用注册编解码器(registered codec)
对字符串进行编码。编解码器由 encoding 参数指定,默认值为’utf-8’。第二
个 errors 参数指定错误的处理方案,可以取’strict’(默认值)、‘ignore’、
‘replace’、‘xmlcharrefreplace’或其他任何注册的处理程序(参见内置
codecs 模块的文档)。
• 利用 bytes(source, encoding, errors)构造函数,创建一个新的字节序列。
如果 source 是 str 类型,那么必须指定 encoding 参数,它没有默认值。
encoding 和 errors 参数的用法与 str.encode()方法中的相同。
用类似方法可以将 bytes 表示的二进制数据转换成字符串:
• 利用 bytes.decode(encoding, errors)方法,用注册编解码器对字节进行
解码。这一方法的参数含义及其默认值与 str.encode()相同。
• 利用 str(source, encoding, error)构造函数,创建一个新的字符串实例。
与 bytes()构造函数类似,如果 source 是字节序列的话,必须指定 str 函数的
encoding 参数,它没有默认值。
实现细节
Python 字符串是不可变的。字节序列也是如此。这一事实很重要,因为它既有优点又有缺点。它还会影响 Python 高效处理字符串的方式。由于不变性,字符串可以作为字典的键或
set 的元素,因为一旦初始化之后字符串的值就不会改变。另一方面,每当需要修改过的字符
串时(即使只是微小的修改),都需要创建一个全新的字符串实例。幸运的是,bytearray
是 bytes 的可变版本,不存在这样的问题。字节数组可以通过元素赋值来进行原处修改(无
需创建新对象),其大小也可以像列表一样动态地变化(利用 append、pop、inseer 等方法)。
字符串拼接
由于 Python 字符串是不可变的,在需要合并多个字符串实例时可能会产生一些问题。
如前所述,拼接任意不可变序列都会生成一个新的序列对象。思考下面这个例子,利用多
个字符串的重复拼接操作来创建一个新字符串:
s = “”
for substring in substrings:
s += substring
这会导致运行时间成本与字符串总长度成二次函数关系。换句话说,这种方法效率极
低。处理这种问题可以用 str.join()方法。它接受可迭代的字符串作为参数,返回合并
后的字符串。由于这是一个方法,实际的做法是利用空字符串来调用它:
s = “”.join(substrings)
字符串的这一方法还可以用于在需要合并的多个子字符串之间插入分隔符,看下面这
个例子:
‘,’.join([‘some’, ‘comma’, ‘separated’, ‘values’])
‘some,comma,separated,values’
需要记住,仅仅因为 join()方法速度更快(对于大型列表来说更是如此),并不意味
着在所有需要拼接两个字符串的情况下都应该使用这一方法。虽然这是一种广为认可的做
法,但并不会提高代码的可读性。可读性是很重要的!在某些情况下,join()的性能可能
还不如利用加法的普通拼接,下面举几个例子。
• 如果子字符串的数量很少,而且已经包含在某个可迭代对象中,那么在某些情况下,
创建一个新序列来进行拼接操作的开销可能会超过使用 join()节省下来的开销。
• 在拼接短的字面值时,由于 CPython 中的常数折叠(constant folding),一些复杂的
字面值(不只是字符串)在编译时会被转换为更短的形式,例如’a’ + ‘b’ + ‘c’
被转换为’abc’。当然,这只适用于相对短的常量(字面值)。
最后,如果事先知道字符串的数目,可以用正确的字符串格式化方法来保证字符串拼
接的最佳可读性。字符串格式化可以用 str.format()方法或%运算符。如果代码段的性能不是很重要,或者优化字符串拼接节省的开销很小,那么推荐使用字符串格式化作为最
佳方法。