Есть ли что-нибудь в Python, например Java StringBuffer
? Поскольку строки также неизменяемы в Python, их редактирование в циклах будет неэффективным.
Python эквивалент Java StringBuffer?
Ответ 1
Эффективная конкатенация строк в Python - довольно старая статья, и ее основное утверждение о том, что наивная конкатенация гораздо медленнее, чем объединение, больше не действует, поскольку с тех пор эта часть была оптимизирована в CPython:
Подробности реализации CPython: если s и t являются обеими строками, некоторые реализации Python, такие как CPython, обычно могут выполнять оптимизацию на месте для назначений вида s = s + t или s + = t. Когда это применимо, эта оптимизация делает квадратичное время выполнения гораздо менее вероятным. Эта оптимизация зависит как от версии, так и от реализации. Для кода, чувствительного к производительности, предпочтительно использовать метод str.join(), который обеспечивает согласованную производительность линейной конкатенации между версиями и реализациями. @http://docs.python.org/2/library/stdtypes.html
Я немного адаптировал их код и получил следующие результаты на моей машине:
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
все еще побеждает Конкат, но незначительно- понимание списка происходит быстрее, чем зацикливание (при создании списка)
- присоединение к генераторам происходит медленнее присоединения к спискам
- другие методы бесполезны (если вы не делаете что-то особенное)
Ответ 2
Возможно, используйте bytearray:
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'
Привлекательность использования bytearray заключается в его эффективности использования памяти и удобном синтаксисе. Это также может быть быстрее, чем использование временного списка:
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
Ответ 3
Зависит от того, что вы хотите сделать. Если вам нужна измененная последовательность, встроенный тип list
- ваш друг, а переход от str к списку и обратно выполняется так же просто, как:
mystring = "abcdef"
mylist = list(mystring)
mystring = "".join(mylist)
Если вы хотите построить большую строку, используя цикл for, пифонический путь обычно заключается в создании списка строк, а затем их объединения вместе с соответствующим разделителем (linebreak или любым другим).
Кроме того, вы также можете использовать некоторую систему текстовых шаблонов, или парсер или любой другой специализированный инструмент, наиболее подходящий для работы.
Ответ 4
Ранее предоставленные ответы почти всегда лучше. Однако иногда строка создается во многих вызовах методов и/или циклах, поэтому не обязательно естественным создавать список строк, а затем присоединяться к ним. И так как нет никакой гарантии, что вы используете CPython или что оптимизация CPython будет применяться, тогда другой подход - просто использовать печать!
Вот пример вспомогательного класса, хотя вспомогательный класс тривиален и, вероятно, не нужен, он служит для иллюстрации подхода (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'
Ответ 5
эта ссылка может быть полезна для конкатенации в 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
Ответ 6
Просто тест, который я запускаю на python 3.6.2, показывающий, что "join" все еще выигрывает BIG!
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
Ответ 7
Я добавил в код 2 Roee Gavirel дополнительные тесты, которые убедительно показывают, что объединение списков в строки происходит не быстрее, чем s + = "что-то".
Результаты:
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!")