簡體   English   中英

Python 等效於 Java StringBuffer?

[英]Python equivalent of Java StringBuffer?

Python 中是否有類似 Java 的StringBuffer的東西? 由於字符串在 Python 中也是不可變的,因此在循環中編輯它們效率低下。

蟒蛇 3

文檔

連接不可變序列總是會產生一個新對象。 這意味着通過重復串聯構建序列將在總序列長度中產生二次運行成本。 要獲得線性運行時成本,您必須切換到以下替代方案之一:如果連接 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

蟒蛇 2

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM