|
|
51CTO旗下网站
|
|
移动端

2.1.1 字符串与字节

《Python高级编程(第2版)》第2章语法最佳实践——类级别以下,本章将介绍现在这门语言的语法中最重要的元素,以及它们的使用技巧。本节为大家介绍字符串与字节。

作者:张亮/阿信 译来源:人民邮电出版社|2018-01-29 18:09

2.1.1 字符串与字节

对于只用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范围内的整数。一开始可能会有点糊涂,因为其打印结果与字符串非常相似:

  1. >>> print(bytes([102, 111, 111]))  
  2. b'foo' 

对于bytes和bytearray,在转换为另一种序列类型(例如list或tuple)时可以显示出其本来面目:

  1. >>> list(b'foo bar')  
  2. [102, 111, 111, 32, 98, 97, 114]  
  3. >>> tuple(b'foo bar')  
  4. (102, 111, 111, 32, 98, 97, 114) 

许多关于Python 3的争议都是关于打破字符串的向后兼容和Unicode的处理方式。从Python 3.0开始,所有没有前缀的字符串都是Unicode。因此,所有用单引号(')、双引号(")或成组的3个引号(单引号或双引号)包围且没有前缀的值都表示str数据类型:

  1. >>> type("some string")  
  2. < class 'str' > 

在Python 2中,Unicode需要有u前缀(例如u"some string")。从Python 3.3开始,为保证向后兼容,仍然可以使用这个前缀,但它在Python 3中没有任何语法上的意义。

前面的一些例子中已经提到过字节,但为了保持前后一致,我们来明确介绍它的语法。字节也被单引号、双引号或三引号包围,但必须有一个b或B前缀:

  1. >>> type(b"some bytes")  
  2. < 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 3中的变化,有些人倾向于将bytes实例称为字节字符串。这主要是由于历史原因——Python 3中的bytes是与Python 2中的str类型最为接近的序列类型(但并不完全相同)。不过bytes实例是字节序列,也不需要表示文本数据。所以为了避免混淆,虽然bytes实例与字符串具有相似性,但建议始终将其称为bytes或字节序列。Python 3中字符串的概念是为文本数据准备的,现在始终是str类型。

1.实现细节

Python字符串是不可变的。字节序列也是如此。这一事实很重要,因为它既有优点又有缺点。它还会影响Python高效处理字符串的方式。由于不变性,字符串可以作为字典的键或set的元素,因为一旦初始化之后字符串的值就不会改变。另一方面,每当需要修改过的字符串时(即使只是微小的修改),都需要创建一个全新的字符串实例。幸运的是,bytearray是bytes的可变版本,不存在这样的问题。字节数组可以通过元素赋值来进行原处修改(无需创建新对象),其大小也可以像列表一样动态地变化(利用append、pop、inseer等方法)。

2.字符串拼接

由于Python字符串是不可变的,在需要合并多个字符串实例时可能会产生一些问题。如前所述,拼接任意不可变序列都会生成一个新的序列对象。思考下面这个例子,利用多个字符串的重复拼接操作来创建一个新字符串:

  1. s = "" 
  2. for substring in substrings:  
  3.     s += substring 

这会导致运行时间成本与字符串总长度成二次函数关系。换句话说,这种方法效率极低。处理这种问题可以用str.join()方法。它接受可迭代的字符串作为参数,返回合并后的字符串。由于这是一个方法,实际的做法是利用空字符串来调用它:

  1. s = "".join(substrings) 

字符串的这一方法还可以用于在需要合并的多个子字符串之间插入分隔符,看下面这个例子:

  1. >>> ','.join(['some', 'comma', 'separated', 'values'])  
  2. 'some,comma,separated,values' 

需要记住,仅仅因为join()方法速度更快(对于大型列表来说更是如此),并不意味着在所有需要拼接两个字符串的情况下都应该使用这一方法。虽然这是一种广为认可的做法,但并不会提高代码的可读性。可读性是很重要的!在某些情况下,join()的性能可能还不如利用加法的普通拼接,下面举几个例子。

如果子字符串的数量很少,而且已经包含在某个可迭代对象中,那么在某些情况下,创建一个新序列来进行拼接操作的开销可能会超过使用join()节省下来的开销。

在拼接短的字面值时,由于CPython中的常数折叠(constant folding),一些复杂的字面值(不只是字符串)在编译时会被转换为更短的形式,例如'a' + 'b' + 'c'被转换为'abc'。当然,这只适用于相对短的常量(字面值)。
最后,如果事先知道字符串的数目,可以用正确的字符串格式化方法来保证字符串拼接的最佳可读性。字符串格式化可以用str.format()方法或%运算符。如果代码段的性能不是很重要,或者优化字符串拼接节省的开销很小,那么推荐使用字符串格式化作为最佳方法。

常数折叠和窥孔优化程序 

CPython对编译过的源代码使用窥孔优化程序来提高其性能。这种优化程序直接对Python字节码实现了许多常见的优化。如上所述,常数折叠就是其功能之一。生成常数的长度不得超过一个固定值。在Python 3.5中这个固定值仍然是 20。不管怎样,这个具体细节只是为了满足读者的好奇心而已,并不能在日常编程中使用。窥孔优化程序还实现了许多有趣的优化,详细信息请参见Python源代码中的Python/peephole.c文件。

喜欢的朋友可以添加我们的微信账号:

51CTO读书频道二维码


51CTO读书频道活动讨论群:365934973

【责任编辑:book TEL:(010)68476606】

回书目   上一节   下一节
点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

网管员必读——网络组建

本书以一个模拟局域网组建为思路,介绍了与局域网组建各主要方面相关的知识及组建、配置方法。本书所介绍的内容主要包括:局域网组建规划、...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊