上一篇文章: Python實用技法第32篇:對齊文本字符串
下一篇文章:
問題
我們想將許多小字符串合併成一個大的字符串。
解決方案
如果想要合併的字符串在一個序列或可迭代對象中,那麼將它們合併起來的最快方法就是使用join()方法。示例如下:
>>> parts = ['Is', 'Chicago', 'Not', 'Chicago?']
>>> ' '.join(parts)
'Is Chicago Not Chicago?'
>>> ','.join(parts)
'Is,Chicago,Not,Chicago?'
>>> ''.join(parts)
'IsChicagoNotChicago?'
>>>
初看上去語法可能顯得有些怪異,但是join()操作其實是字符串對象的一個方法。這麼設計的部分原因是因爲想要合併在一起的對象可能來自於各種不同的數據序列,比如列表、元組、字典、文件、集合或生成器,如果單獨在每一種序列對象中實現一個join()方法就顯得太冗餘了。因此只需要指定想要的分隔字符串,然後在字符串對象上使用join()方法將文本片段粘合在一起就可以了。
如果只是想連接一些字符串,一般使用+操作符就足夠完成任務了:
>>> a = 'Is Chicago'
>>> b = 'Not Chicago?'
>>> a + ' ' + b
'Is Chicago Not Chicago?'
>>>
針對更加複雜的字符串格式化操作,+操作符同樣可以作爲format()的替代,很好地完成任務:
>>> print('{} {}'.format(a,b))
Is Chicago Not Chicago?
>>> print(a + ' ' + b)
Is Chicago Not Chicago?
>>>
如果打算在源代碼中將字符串字面值合併在一起,可以簡單地將它們排列在一起,中間不加+操作符。示例如下:
>>> a = 'Hello' 'World'
>>> a
'HelloWorld'
>>>
討論
字符串連接這個主題可能看起來還沒有高級到要用一整節的篇幅來講解,但是程序員常常會在這個問題上做出錯誤的編程選擇,使得他們的代碼性能受到影響。
最重要的一點是要意識到使用+操作符做大量的字符串連接是非常低效的,原因是由於內存拷貝和垃圾收集產生的影響。特別是你絕不會想寫出這樣的字符串連接代碼:
s = ''
for p in parts:
s += p
這種做法比使用join()方法要慢上許多。主要是因爲每個+=操作都會創建一個新的字符串對象。我們最好先收集所有要連接的部分,最後再一次將它們連接起來。
一個相關的技巧(很漂亮的技巧)是利用生成器表達式(見1.19節)在將數據轉換爲字符串的同時完成連接操作。示例如下:
>>> data = ['ACME', 50, 91.1]
>>> ','.join(str(d) for d in data)
'ACME,50,91.1'
>>>
對於不必要的字符串連接操作也要引起重視。有時候在技術上並非必需的時候,程序員們也會忘乎所以地使用字符串連接操作。例如在打印的時候:
print(a + ':' + b + ':' + c) # Ugly
print(':'.join([a, b, c])) # Still ugly
print(a, b, c, sep=':') # Better
將字符串連接同I/O操作混合起來的時候需要對應用做仔細的分析。例如,考慮如下兩段代碼:
# Version 1 (string concatenation)
f.write(chunk1 + chunk2)
# Version 2 (separate I/O operations)
f.write(chunk1)
f.write(chunk2)
如果這兩個字符串都很小,那麼第一個版本的代碼能帶來更好的性能,這是因爲執行一次I/O系統調用的固有開銷就很高。另一方面,如果這兩個字符串都很大,那麼第二個版本的代碼會更加高效。因爲這裏避免了創建大的臨時結果,也沒有對大塊的內存進行拷貝。這裏必須再次強調,你需要對自己的數據做分析,以此才能判定哪一種方法可以獲得最好的性能。
最後但也是最重要的是,如果我們編寫的代碼要從許多短字符串中構建輸出,則應該考慮編寫生成器函數,通過yield關鍵字生成字符串片段。示例如下:
def sample():
yield 'Is'
yield 'Chicago'
yield 'Not'
yield 'Chicago?'
關於這種方法有一個有趣的事實,那就是它不會假設產生的片段要如何組合在一起。比如說可以用join()將它們簡單的連接起來:
text = ''.join(sample())
或者,也可以將這些片段重定向到I/O:
for part in sample():
f.write(part)
又或者我們能以混合的方式將I/O操作智能化地結合在一起:
def combine(source, maxsize):
parts = []
size = 0
for part in source:
parts.append(part)
size += len(part)
if size > maxsize:
yield ''.join(parts)
parts = []
size = 0
yield ''.join(parts)
for part in combine(sample(), 32768):
f.write(part)
關鍵在於這裏的生成器函數並不需要知道精確的細節,它只是產生片段而已。