web-dev-qa-db-fra.com

Python équivalent de Java StringBuffer?

Y at-il quelque chose dans Python comme le StringBuffer de Java??) Les chaînes étant immuables dans Python aussi, les éditer dans des boucles serait inefficace.

63
user2902773

La concaténation efficace de chaînes en Python est un article plutôt ancien et son énoncé principal selon lequel la concaténation naïve est beaucoup plus lente que la jointure n'est plus valide, car cette partie a été optimisée dans CPython depuis:

Détail de l’implémentation CPython: Si s et t sont deux chaînes, certaines implémentations Python telles que CPython peuvent généralement effectuer une optimisation sur place pour les assignations de la forme s = s + t ou s + = t Cette optimisation dépend de la version et de l’implémentation. Pour le code sensible aux performances, il est préférable d’utiliser la méthode str.join () qui garantit des performances de concaténation linéaire cohérentes entre les versions et implémentations. @ http://docs.python.org/2/library/stdtypes.html

J'ai un peu adapté leur code et obtenu les résultats suivants sur ma machine:

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)

Résultats:

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

Conclusions:

  • join l'emporte toujours sur concat, mais marginalement
  • les compréhensions de liste sont plus rapides que les boucles
  • rejoindre les générateurs est plus lent que rejoindre les listes
  • les autres méthodes sont inutiles (sauf si vous faites quelque chose de spécial)
67
georg

Peut-être utiliser un 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'

L'attrait de l'utilisation d'un bytearray est son efficacité de mémoire et sa syntaxe pratique. Cela peut aussi être plus rapide que d’utiliser une liste temporaire:

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

Notez que la différence de vitesse est en grande partie imputable à la création du conteneur:

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
11
unutbu

Cela dépend de ce que vous voulez faire. Si vous voulez une séquence mutable, le type intégré list est votre ami, et aller de str à list et back est aussi simple que:

 mystring = "abcdef"
 mylist = list(mystring)
 mystring = "".join(mylist)

Si vous souhaitez créer une chaîne de grande taille à l'aide d'une boucle for, la méthode Pythonic consiste généralement à créer une liste de chaînes, puis à les associer avec le séparateur approprié (saut de ligne ou autre).

Sinon, vous pouvez également utiliser un système de modèle de texte, un analyseur syntaxique ou tout autre outil spécialisé qui convient le mieux au travail.

11

Les réponses fournies précédemment sont presque toujours les meilleures. Cependant, la chaîne est parfois constituée de nombreux appels de méthodes et/ou de boucles, il n'est donc pas naturel de créer une liste de lignes puis de les joindre. Et comme rien ne garantit que vous utilisez CPython ou que l'optimisation de CPython sera appliquée, une autre approche consiste simplement à utiliser print!

Voici un exemple de classe d'assistance, bien que la classe d'aide soit triviale et probablement inutile, elle sert à illustrer l'approche (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
rhaertel80

Juste un test sur lequel je lance python 3.6.2 montrant que "rejoindre" gagne toujours 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!")

Et la sortie était:

with format done in 17.991968870162964s
with s done in 18.36879801750183s
with list and join done in 0.12142801284790039s
2
Roee Gavirel

ce lien pourrait être utile pour la concaténation en python

http://pythonadventures.wordpress.com/2010/09/27/stringbuilder/

exemple du lien ci-dessus:

def g():
    sb = []
    for i in range(30):
        sb.append("abcdefg"[i%7])

    return ''.join(sb)

print g()   

# abcdefgabcdefgabcdefgabcdefgab
2
Kamlesh Arya

J'ai ajouté au code de Roee Gavirel 2 tests supplémentaires qui montrent de manière concluante que joindre des listes à des chaînes n'est pas plus rapide que s + = "quelque chose".

Résultats:

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

Code:

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!")
1
Martlark