1.6 合并字符串
任务
有一些小的字符串,想把这些字符串合并成一个大字符串。
解决方案
要把一系列小字符串连接成一个大字符串,可以使用字符串操作符join。假如pieces是一个字符串列表,想把列表中所有的字符串按顺序拼接成一个大字符串,可以这么做:
largeString = ''.join(pieces)
如果想把存储在一些变量中的字符串片段拼接起来,那么使用字符串格式化操作符%会更好一些:
largeString = '%s%s something %s yet more' % (small1, small2, small3)
讨论
Python中,+操作符也能够将字符串拼接起来,从而实现类似的功能。假如有一些保存在变量中的字符串片段,使用下面这种代码似乎是一种很自然的方式:
largeString = small1 + small2 + ' something ' + small3 + ' yet more'
类似地,如果有一个小字符串序列,假设叫做pieces,那么很自然地,可以像这样编写代码:
largeString = ''
for piece in pieces:
largeString += piece
或者,用完全等同但却更加漂亮和紧凑的方式:
import operator
largeString = reduce(operator.add, pieces, '')
不过,不要认为上述例子中给出的方法已经足够好了,上面给出的方法都有许多值得推敲的地方。
Python中的字符串对象是无法改变的。任何对字符串的操作,包括字符串拼接,都将产生一个新的字符串对象,而不是修改原有的对象。因此拼接N个字符串将涉及创建并丢弃N-1个中间结果。当然,不创建中间结果的操作会有更佳的性能,但往往不能一步到位地取得最终结果。
如果有少量字符串(尤其是那些绑定到变量上的)需要拼接,甚至有时还需要添加一些额外的信息,Python的字符串格式化操作符%通常是更好的选择。性能对这种操作完全不是一个问题。和使用多个+操作符相比,%操作符还有一些其他的潜在优点。一旦习惯了它,%也会让你的代码的可读性更好。也无须再对所有的非字符串(如数字)部分调用str,因为格式指定符%s已经暗中做完了这些工作。另一个优点是,还可以使用除%s之外的其他格式指定符,这样可以实现更多的格式要求,比如将浮点数转化为字符串的表示时,可以控制它的有效位数。
什么是“序列”? Python并没有一个特别的类型叫做sequence,但序列是Python中一个非常常用的术语。序列,严格地讲,是一个可以迭代的容器,可以从中取出一定数目的子项,也可以一次取出一个,而且它还支持索引、切片,还可以传递给内建函数len(返回容器中子项的数目)。Python的list就是“序列”,你已经见过多次了,但还有很多其他的“序列”(string、unicode对象、tuple、array.array等)。 通常,一个对象即使不支持索引、切片和len。只要具有一次获得一项的迭代能力,对应用而言就已经够用了。这叫做可迭代对象(或者,如果我们把范围限定在拥有有限子项的情况下,那就叫做有边界可迭代对象)。可迭代对象不是序列,而是如字典(迭代操作将以任意顺序每次取得一个key)、文件对象(迭代操作将给出文本文件的行数)等,还有其他一些,包括迭代器和生成器等。任何可迭代对象都能用在for循环语句以及一些等价的环境中(Python 2.4的生成器表达式、列表推导中的for子句、以及很多内建的方法,比如min、max、zip、sum、str.join等)。 在http://www.python.org/moin/PythonGlossary ,可以发现一个词汇表,Python Glossary,它能够帮助你了解很多术语。虽然本书的编辑尽量严格按照那个词汇表的描述来使用术语,仍可能发现本书在很多地方提到了序列、可迭代对象、甚至列表,实际上,严格地讲,我们都应该指明为有边界的可迭代对象。比如,在本节的开头,我们说“一个小字符串的序列”,实际上那是一个有边界的字符串的可迭代对象。在全书使用“有边界的可迭代对象”这样的术语给人的感觉像是在读一本数学书,而不是一本实践性很强的编程书!所以我们决定采用略微偏离严格术语系统的词,这样有助于获得更好的可读性,同时也能够更好地体现本书的多元化。最后结果还不错,根据上下文环境,那些不怎么严密的术语的真实含义仍然能够被清晰地表达出来。
当一个序列中包含了很多的小字符串的时候,性能就变成了一个很现实的问题。在内部使用了+或者+=(和内建函数reduce作用相同,但是更漂亮)的循环所需要的时间跟需要累加的字符数的平方成正比,因为分配空间并填充一个大字符串所需要的时间大致正比于该字符串的长度。幸好Python提供了另一个更好的选择。对于字符串对象s的join方法,我们可以传入一个字符串序列作为其参数,它将返回一个由字符串序列中所有子项字符串拼接而成的大字符串,而且这个过程中只使用了一个s的拷贝用于串接所有的子项。举个例子,".join(pieces)把pieces中所有的子项一口吞下,而无须产生子项之间的中间结果,再比如,', '.join(pieces)拼接了所有的子项字符串,并在邻接的两项之间插入了一个逗号和空格。这是一种快速、整洁、优雅且兼具良好可读性的合并大字符串的方法。
但有时并不是所有的数据在一开始就已经就位,比如数据可能来自于输入或计算,这时可以使用一个list作为中间数据结构来容纳它们(可以使用list的append或extend方法在末尾添加新的数据)。在取得了所有的数据之后,再调用".join(thelist)就可以得到合并之后的大字符串。在我能教给你的Python的字符串处理的各种技巧和方法中,这是最重要的一条:很多Python程序效能低下的原因是由于它们使用了+和+=来创建大字符串。因此,一定要提醒自己永远不要使用那种做法,而应该使用本节推荐的".join方法。
Python 2.4在这个问题的改善上做了很多工作,在Python 2.4中使用+=,性能损失要比以前的版本小一些。但".join仍然要快许多,而且在各方面都更具优势,至少对于新人和粗心的开发人员来讲,它消耗的时钟周期也更少。类似地,psyco(一种特制的just-in-time[JIT] Python编译器,请查看http://psyco.sourceforge.net/ )能更大幅度地减少+=带来的性能损失。不过,我还是要强调,’’.join依然是最值得选择的方式。