Python эквивалент Java StringBuffer?

Есть ли что-нибудь в Python, например Java StringBuffer? Поскольку строки также неизменяемы в Python, их редактирование в циклах будет неэффективным.

Ответ 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!")