本文中所有代码均运行在Python 2.7上
在日常编程过程之中,我们难免要去处理一些字符串(string)。Python
中的字符串是不可变对象(immutable):一经创建就不能再改变。这也带来一个显而易见的副作用:每次字符串的连接都会产生新的字符串对象。
我们经常使用的字符串连接方法有+
操作符连接和join
连接。两者适用范围略有不同:前者的操作对象是两个字符串,而后者的操作对象是一个可迭代对象(iterable),前者的操作结果没有分隔符,而后者则可以指定分隔符。
除了这些区别之外,两者在性能上有没有区别呢?
接下来用一个例子说明:
import timeit
test_str =[ 'abc'*100 for _ in range(10**7)]
def join_test(test_str):
return ' '.join(test_str)
def add_test(test_str):
result = ''
for one_str in test_str:
result += one_str
return result
>>> joinTimer = timeit.Timer("join_test(test_str)", "from __main__ import join_test, test_str")
>>> print "join test:{join_result}".format(join_result=str(joinTimer.timeit(number=100)))
join_test:47.199542
>>> addTimer = timeit.Timer("add_test(test_str)", "from __main__ import add_test, test_str")
>>> print "add test:{add_result}".format(join_result=str(addTimer.timeit(number=100)))
join_test:17926.728402
可见,两者的时间差距还是挺大的(鉴于这个时间过久,建议司机测试时减少字符串的长度和个数,以及重复测试的次数,以减少对于内存和CPU资源的消耗)。
那两者的性能差异的原因又是什么呢?
- 当时用
+
时,由于字符串是不可变对象,它的实际工作过程是:没执行一次+
(实际是调用__add__()
)操作就申请开辟一块新的内存空间,将右值追加在上次操作结果的后面,一并写到新的内存空间中。因此,当进行n
个字符串连接时,会产生n-1
个中间过程,每个中间过程都要去申请内存块并进行写操作,而且随着内容的增多,申请内存块和写的操作会耗时更多。 - 当使用
join
时,它会预先计算好所需的内存空间,一次性申请好,将操作对象一次连接写入内存块。
由此可见,再进行大量字符串连接的时候,应该优先使用join
。
接下来,来一起看一下字符串格式化的问题。python
中字符串常使用的方法有%
和str.format
,其中format
是python
的2.5版本引入的特性。
简单来说,两者相比的情况下format
是一种更为简便的选择,比如下面的代码:
对于一个元组来说(hobbies=('nba', 'fiba', 'fifa')
)
>>> "my habbies are:%s"% hobbies
这种操作会是引起TypeError
的,而必须写成下面的样子:
>>> "my habbies are:%s"% (hobbies,)
的确可以工作,但非常的不pythonic
,任何pythonista
都忍不了。
format
就没有相关的困扰。
而且,%
操作要求变量和参数的位置要对应,而format这可以使用类似dict
的方式去指定参数顺序。况且,format
的方式显然更加直接,可读性更好。最后的原因,format
是被官方推荐的方法,在今后会逐步取代%
。更多内容
最后来谈一个在Python 3.6
中新加的特性Formatted string literals,简称fstring,即通过f
前缀来实现字符串插值:
>>> hobby = 'NBA'
>>> f"My hobby is {hobby}"
My hobby is NBA
这种字符串插值的语法在Scala和Swift中也有,算是一种语法糖,Python 3.6
借鉴了其他语言并引入了这种语法,相比也是从可读性的角度来思考。更多Python 3.6新特性
网友评论