[英]Python equivalent of Java StringBuffer?
Python 中是否有類似 Java 的StringBuffer
的東西? 由於字符串在 Python 中也是不可變的,因此在循環中編輯它們效率低下。
從文檔:
連接不可變序列總是會產生一個新對象。 這意味着通過重復串聯構建序列將在總序列長度中產生二次運行成本。 要獲得線性運行時成本,您必須切換到以下替代方案之一:如果連接 str 對象,您可以構建一個列表並在末尾使用 str.join() 或者寫入 io.StringIO 實例並檢索其值完成時
嘗試比較幾個選項的運行時間:
import sys
import timeit
from io import StringIO
from array import array
def test_concat():
out_str = ''
for _ in range(loop_count):
out_str += 'abc'
return out_str
def test_join_list_loop():
str_list = []
for _ in range(loop_count):
str_list.append('abc')
return ''.join(str_list)
def test_array():
char_array = array('b')
for _ in range(loop_count):
char_array.frombytes(b'abc')
return str(char_array.tostring())
def test_string_io():
file_str = StringIO()
for _ in range(loop_count):
file_str.write('abc')
return file_str.getvalue()
def test_join_list_compr():
return ''.join(['abc' for _ in range(loop_count)])
def test_join_gen_compr():
return ''.join('abc' for _ in range(loop_count))
loop_count = 80000
print(sys.version)
res = {}
for k, v in dict(globals()).items():
if k.startswith('test_'):
res[k] = timeit.timeit(v, number=10)
for k, v in sorted(res.items(), key=lambda x: x[1]):
print('{:.5f} {}'.format(v, k))
結果
3.7.5 (default, Nov 1 2019, 02:16:32)
[Clang 11.0.0 (clang-1100.0.33.8)]
0.03738 test_join_list_compr
0.05681 test_join_gen_compr
0.09425 test_string_io
0.09636 test_join_list_loop
0.11976 test_concat
0.19267 test_array
Python中的Efficient String Concatenation是一篇相當老的文章,它的主要陳述是 naive concatenation 比 join 慢得多,因為這部分已經在 CPython 中進行了優化。 從文檔:
CPython 實現細節:如果 s 和 t 都是字符串,則某些 Python 實現(例如 CPython)通常可以對 s = s + t 或 s += t 形式的賦值執行就地優化。 在適用時,這種優化使二次運行時間的可能性大大降低。 此優化取決於版本和實現。 對於性能敏感的代碼,最好使用 str.join() 方法,以確保跨版本和實現的一致線性串聯性能。
我稍微修改了他們的代碼,並在我的機器上得到了以下結果:
from cStringIO import StringIO
from UserString import MutableString
from array import array
import sys, timeit
def method1():
out_str = ''
for num in xrange(loop_count):
out_str += `num`
return out_str
def method2():
out_str = MutableString()
for num in xrange(loop_count):
out_str += `num`
return out_str
def method3():
char_array = array('c')
for num in xrange(loop_count):
char_array.fromstring(`num`)
return char_array.tostring()
def method4():
str_list = []
for num in xrange(loop_count):
str_list.append(`num`)
out_str = ''.join(str_list)
return out_str
def method5():
file_str = StringIO()
for num in xrange(loop_count):
file_str.write(`num`)
out_str = file_str.getvalue()
return out_str
def method6():
out_str = ''.join([`num` for num in xrange(loop_count)])
return out_str
def method7():
out_str = ''.join(`num` for num in xrange(loop_count))
return out_str
loop_count = 80000
print sys.version
print 'method1=', timeit.timeit(method1, number=10)
print 'method2=', timeit.timeit(method2, number=10)
print 'method3=', timeit.timeit(method3, number=10)
print 'method4=', timeit.timeit(method4, number=10)
print 'method5=', timeit.timeit(method5, number=10)
print 'method6=', timeit.timeit(method6, number=10)
print 'method7=', timeit.timeit(method7, number=10)
結果:
2.7.1 (r271:86832, Jul 31 2011, 19:30:53)
[GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)]
method1= 0.171155929565
method2= 16.7158739567
method3= 0.420584917068
method4= 0.231794118881
method5= 0.323612928391
method6= 0.120429992676
method7= 0.145267963409
結論:
join
仍然勝過 concat,但略勝一籌取決於你想做什么。 如果你想要一個可變序列,內置list
類型就是你的朋友,從 str 到 list 再返回就像這樣簡單:
mystring = "abcdef"
mylist = list(mystring)
mystring = "".join(mylist)
如果你想使用 for 循環構建一個大字符串,pythonic 的方法通常是構建一個字符串列表,然后用適當的分隔符(換行符或其他什么)將它們連接在一起。
否則,您還可以使用一些文本模板系統、解析器或任何最適合該工作的專用工具。
也許使用字節數組:
In [1]: s = bytearray('Hello World')
In [2]: s[:5] = 'Bye'
In [3]: s
Out[3]: bytearray(b'Bye World')
In [4]: str(s)
Out[4]: 'Bye World'
使用字節數組的吸引力在於它的內存效率和方便的語法。 它也可以比使用臨時列表更快:
In [36]: %timeit s = list('Hello World'*1000); s[5500:6000] = 'Bye'; s = ''.join(s)
1000 loops, best of 3: 256 µs per loop
In [37]: %timeit s = bytearray('Hello World'*1000); s[5500:6000] = 'Bye'; str(s)
100000 loops, best of 3: 2.39 µs per loop
請注意,速度的大部分差異歸因於容器的創建:
In [32]: %timeit s = list('Hello World'*1000)
10000 loops, best of 3: 115 µs per loop
In [33]: %timeit s = bytearray('Hello World'*1000)
1000000 loops, best of 3: 1.13 µs per loop
之前提供的答案幾乎總是最好的。 但是,有時字符串是跨許多方法調用和/或循環構建的,因此構建行列表然后加入它們不一定很自然。 並且由於不能保證您使用的是 CPython,或者 CPython 的優化將適用,另一種方法是使用print
!
下面是一個示例助手類,雖然助手類是微不足道的,可能是不必要的,但它有助於說明該方法(Python 3):
import io
class StringBuilder(object):
def __init__(self):
self._stringio = io.StringIO()
def __str__(self):
return self._stringio.getvalue()
def append(self, *objects, sep=' ', end=''):
print(*objects, sep=sep, end=end, file=self._stringio)
sb = StringBuilder()
sb.append('a')
sb.append('b', end='\n')
sb.append('c', 'd', sep=',', end='\n')
print(sb) # 'ab\nc,d\n'
此鏈接可能對 python 中的連接有用
http://pythonadventures.wordpress.com/2010/09/27/stringbuilder/
上面鏈接的例子:
def g():
sb = []
for i in range(30):
sb.append("abcdefg"[i%7])
return ''.join(sb)
print g()
# abcdefgabcdefgabcdefgabcdefgab
只是我在 python 3.6.2 上運行的一個測試表明“加入”仍然大獲全勝!
from time import time
def _with_format(i):
_st = ''
for i in range(0, i):
_st = "{}{}".format(_st, "0")
return _st
def _with_s(i):
_st = ''
for i in range(0, i):
_st = "%s%s" % (_st, "0")
return _st
def _with_list(i):
l = []
for i in range(0, i):
l.append("0")
return "".join(l)
def _count_time(name, i, func):
start = time()
r = func(i)
total = time() - start
print("%s done in %ss" % (name, total))
return r
iterationCount = 1000000
r1 = _count_time("with format", iterationCount, _with_format)
r2 = _count_time("with s", iterationCount, _with_s)
r3 = _count_time("with list and join", iterationCount, _with_list)
if r1 != r2 or r2 != r3:
print("Not all results are the same!")
輸出是:
with format done in 17.991968870162964s
with s done in 18.36879801750183s
with list and join done in 0.12142801284790039s
我在 Roee Gavirel 的代碼中添加了 2 個額外的測試,最終表明將列表加入字符串並不比 s += "something" 快。
結果:
Python 2.7.15rc1
Iterations: 100000
format done in 0.317540168762s
%s done in 0.151262044907s
list+join done in 0.0055148601532s
str cat done in 0.00391721725464s
Python 3.6.7
Iterations: 100000
format done in 0.35594654083251953s
%s done in 0.2868080139160156s
list+join done in 0.005924701690673828s
str cat done in 0.0054128170013427734s
f str done in 0.12870001792907715s
代碼:
from time import time
def _with_cat(i):
_st = ''
for i in range(0, i):
_st += "0"
return _st
def _with_f_str(i):
_st = ''
for i in range(0, i):
_st = f"{_st}0"
return _st
def _with_format(i):
_st = ''
for i in range(0, i):
_st = "{}{}".format(_st, "0")
return _st
def _with_s(i):
_st = ''
for i in range(0, i):
_st = "%s%s" % (_st, "0")
return _st
def _with_list(i):
l = []
for i in range(0, i):
l.append("0")
return "".join(l)
def _count_time(name, i, func):
start = time()
r = func(i)
total = time() - start
print("%s done in %ss" % (name, total))
return r
iteration_count = 100000
print('Iterations: {}'.format(iteration_count))
r1 = _count_time("format ", iteration_count, _with_format)
r2 = _count_time("%s ", iteration_count, _with_s)
r3 = _count_time("list+join", iteration_count, _with_list)
r4 = _count_time("str cat ", iteration_count, _with_cat)
r5 = _count_time("f str ", iteration_count, _with_f_str)
if len(set([r1, r2, r3, r4, r5])) != 1:
print("Not all results are the same!")
在最佳答案中,“Python 中的高效字符串連接”中的鏈接不再鏈接到預期頁面(而是重定向到 tensorflow.org)。 然而,這個 2004 年的頁面引用的確切代碼可能代表該頁面https://waymoot.org/home/python_string/ 。
如果您使用谷歌搜索,您可能已經看過它,因為它首先出現:
efficient python StringBuilder
我不能在評論中留下這個,因為我沒有特權。
Python 為可變字符串或 StringBuffer 提供的最接近的東西可能是來自array
標准庫模塊的 Unicode 類型數組。 在您只想編輯字符串的一小部分的情況下,它會很有用:
modifications = [(2, 3, 'h'), (0, 6, '!')]
n_rows = multiline_string.count('\n')
strarray = array.array('u', multiline_string)
for row, column, character in modifications:
strarray[row * (n_rows + 1) + column] = character
multiline_string = map_strarray.tounicode()
這是我的StringBuffer
實現:
class StringBuffer:
def __init__(self, s:str=None):
self._a=[] if s is None else [s]
def a(self, v):
self._a.append(str(v))
return self
def al(self, v):
self._a.append(str(v))
self._a.append('\n')
return self
def ts(self, delim=''):
return delim.join(self._a)
def __bool__(self): return True
用法:
sb = StringBuffer('{')
for i, (k, v) in enumerate({'k1':'v1', 'k2': 'v2'}.items()):
if i > 0: sb.a(', ')
sb.a('"').a(k).a('": ').a('"').a(v)
sb.a('}')
print(sb.ts('\n'))
這將是 output {"k1": "v1, "k2": "v2}
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.