Wat is de beste manier om een string in Python samen te voegen?

Aangezien de stringvan Python niet kan worden gewijzigd, vroeg ik me af hoe ik een string efficiënter kan samenvoegen?

Ik kan zo schrijven:

s += stringfromelsewhere

of zoals dit:

s = []
s.append(somestring)
later
s = ''.join(s)

Terwijl ik deze vraag schreef, vond ik een goed artikel over het onderwerp.

http://www.skymind.com/~ocrow/python_string/

Maar het zit in Python 2.x., dus de vraag is of er iets is veranderd in Python 3?


Antwoord 1, autoriteit 100%

De bestemanier om een tekenreeks aan een tekenreeksvariabele toe te voegen, is door +of +=te gebruiken. Dit komt omdat het leesbaar en snel is. Ze zijn ook net zo snel, welke je kiest is een kwestie van smaak, de laatste is de meest voorkomende. Hier zijn de tijden met de timeitmodule:

a = a + b:
0.11338996887207031
a += b:
0.11040496826171875

Degenen die echter aanbevelen om lijsten te hebben en ze toe te voegen en vervolgens aan die lijsten toe te voegen, doen dit omdat het toevoegen van een string aan een lijst vermoedelijk erg snel gaat in vergelijking met het verlengen van een string. En dit kan in sommige gevallen waar zijn. Hier is er bijvoorbeeld een
miljoen toevoegingen van een tekenreeks van één teken, eerst aan een tekenreeks en vervolgens aan een lijst:

a += b:
0.10780501365661621
a.append(b):
0.1123361587524414

OK, het blijkt dat zelfs als de resulterende string een miljoen tekens lang is, het toevoegen nog steeds sneller was.

Laten we nu eens proberen honderdduizend keer een tekenreeks van duizend tekens toe te voegen:

a += b:
0.41823482513427734
a.append(b):
0.010656118392944336

De eindstring wordt daarom uiteindelijk ongeveer 100 MB lang. Dat ging vrij langzaam, toevoegen aan een lijst ging veel sneller. Dat die timing niet de laatste a.join()bevat. Dus hoe lang zou dat duren?

a.join(a):
0.43739795684814453

Oeps. Het blijkt dat zelfs in dit geval append/join langzamer is.

Dus waar komt deze aanbeveling vandaan? Python 2?

a += b:
0.165287017822
a.append(b):
0.0132720470428
a.join(a):
0.114929914474

Nou, append/join is daar marginaalsneller als je extreem lange strings gebruikt (wat je normaal niet bent, wat zou je een string hebben van 100 MB geheugen?)

Maar de echte clincher is Python 2.3. Waar ik je niet eens de timing zal laten zien, omdat het zo traag is dat het nog niet klaar is. Deze tests duren plotseling minuten. Behalve de append/join, die net zo snel is als onder latere Pythons.

Ja. String aaneenschakeling was erg traag in Python in het stenen tijdperk. Maar op 2.4 is het niet meer (of in ieder geval Python 2.4.7), dus de aanbeveling om append/join te gebruiken werd in 2008 achterhaald, toen Python 2.3 niet meer werd bijgewerkt, en je had ermee moeten stoppen het te gebruiken. 🙂

(Update: Toen ik het testen zorgvuldiger deed, bleek dat het gebruik van +en +=ook sneller is voor twee strings op Python 2.3. aanbeveling om ''.join()te gebruiken moet een misverstand zijn)

Dit is echter CPython. Andere implementaties kunnen andere zorgen hebben. En dit is nog een reden waarom voortijdige optimalisatie de wortel van alle kwaad is. Gebruik geen techniek die “sneller” zou moeten zijn, tenzij u deze eerst meet.

Daarom is de “beste” versie om tekenreeksen samen te voegen het gebruik van + of +=. En als dat traag voor je blijkt te zijn, wat vrij onwaarschijnlijk is, doe dan iets anders.

Dus waarom gebruik ik veel append/join in mijn code? Want soms is het juist duidelijker. Vooral als alles wat je aan elkaar moet voegen, gescheiden moet worden door spaties of komma’s of nieuwe regels.


Antwoord 2, autoriteit 11%

Als u veel waarden aaneenvoegt, dan geen van beide. Het toevoegen van een lijst is duur. Daar kun je StringIO voor gebruiken. Vooral als je het over veel operaties opbouwt.

from cStringIO import StringIO
# python3:  from io import StringIO
buf = StringIO()
buf.write('foo')
buf.write('foo')
buf.write('foo')
buf.getvalue()
# 'foofoofoo'

Als je al een volledige lijst hebt gekregen van een andere bewerking, gebruik dan gewoon de ''.join(aList)

Van de veelgestelde vragen over python: Wat is de meest efficiënte manier om veel strings samen te voegen?

str en bytes-objecten zijn onveranderlijk, daarom worden veel aaneengeschakeld
strings samen is inefficiënt omdat elke aaneenschakeling een nieuwe
object. In het algemene geval zijn de totale runtime-kosten kwadratisch in
de totale stringlengte.

Om veel str-objecten te verzamelen, is het aanbevolen idioom om ze te plaatsen
in een lijst en roep aan het einde str.join() aan:

chunks = []
for s in my_strings:
    chunks.append(s)
result = ''.join(chunks)

(Een ander redelijk efficiënt idioom is om io.Stringio)

te gebruiken

Om veel bytes-objecten te accumuleren, is het aanbevolen idioom om een
ByteArray-object met behulp van in-place-aaneenvoeging (de + = operator):

result = bytearray()
for b in my_bytes_objects:
    result += b

EDIT: Ik was dom en had de resultaten achteruit geplakt, waardoor het lijkt op het toevoegen van een lijst was sneller dan Cstringio. Ik heb ook tests toegevoegd voor ByteArray / Str-vat, evenals een tweede testronde met een grotere lijst met grotere snaren. (Python 2.7.3)

IPYTHON Testvoorbeeld voor grote lijsten van strings

try:
    from cStringIO import StringIO
except:
    from io import StringIO
source = ['foo']*1000
%%timeit buf = StringIO()
for i in source:
    buf.write(i)
final = buf.getvalue()
# 1000 loops, best of 3: 1.27 ms per loop
%%timeit out = []
for i in source:
    out.append(i)
final = ''.join(out)
# 1000 loops, best of 3: 9.89 ms per loop
%%timeit out = bytearray()
for i in source:
    out += i
# 10000 loops, best of 3: 98.5 µs per loop
%%timeit out = ""
for i in source:
    out += i
# 10000 loops, best of 3: 161 µs per loop
## Repeat the tests with a larger list, containing
## strings that are bigger than the small string caching 
## done by the Python
source = ['foo']*1000
# cStringIO
# 10 loops, best of 3: 19.2 ms per loop
# list append and join
# 100 loops, best of 3: 144 ms per loop
# bytearray() +=
# 100 loops, best of 3: 3.8 ms per loop
# str() +=
# 100 loops, best of 3: 5.11 ms per loop

Antwoord 3, autoriteit 9%

In Python >= 3.6, de nieuwe f-stringis een efficiënte manier om een string samen te voegen.

>>> name = 'some_name'
>>> number = 123
>>>
>>> f'Name is {name} and the number is {number}.'
'Name is some_name and the number is 123.'

Antwoord 4, autoriteit 3%

Het gebruik van stringconcatenatie door ‘+’ is DE SLECHTSTE methode van concatenatie in termen van stabiliteit en cross-implementatie omdat het niet alle waarden ondersteunt. PEP8-standaardontmoedigt dit en moedigt het gebruik van format() aan , join() en append() voor langdurig gebruik.

Zoals geciteerd in de gelinkte sectie “Programmeeraanbevelingen”:

Vertrouw bijvoorbeeld niet op CPython’s efficiënte implementatie van in-place tekenreeksaaneenschakeling voor instructies in de vorm a += b of a = a + b. Deze optimalisatie is zelfs in CPython kwetsbaar (het werkt alleen voor sommige typen) en is helemaal niet aanwezig in implementaties die geen refcounting gebruiken. In prestatiegevoelige delen van de bibliotheek moet in plaats daarvan het formulier ”.join() worden gebruikt. Dit zorgt ervoor dat aaneenschakeling plaatsvindt in lineaire tijd over verschillende implementaties.


Antwoord 5, autoriteit 2%

De aanbevolen methode is nog steeds om append en join te gebruiken.


Antwoord 6, autoriteit 2%

Als de tekenreeksen die u aaneenvoegt letterlijke waarden zijn, gebruik dan Letterlijke aaneenschakeling van tekenreeksen

re.compile(
        "[A-Za-z_]"       # letter or underscore
        "[A-Za-z0-9_]*"   # letter, digit or underscore
    )

Dit is handig als je commentaar wilt geven op een deel van een string (zoals hierboven) of als je onbewerkte stringsof driedubbele aanhalingstekens voor een deel van een letterlijke maar niet alle.

Aangezien dit gebeurt op de syntaxislaag, gebruikt het nul-aaneenschakelingsoperatoren.


Antwoord 7, autoriteit 2%

Je schrijft deze functie

def str_join(*args):
    return ''.join(map(str, args))

Dan kun je gewoon bellen waar je maar wilt

str_join('Pine')  # Returns : Pine
str_join('Pine', 'apple')  # Returns : Pineapple
str_join('Pine', 'apple', 3)  # Returns : Pineapple3

Antwoord 8, autoriteit 2%

Zoals @jdi vermeldt, suggereert Python-documentatie om str.joinof io.StringIOte gebruiken voor het samenvoegen van strings. En zegt dat een ontwikkelaar kwadratische tijd moet verwachten van +=in een lus, ook al is er een optimalisatie sinds Python 2.4. Zoals ditantwoord zegt:

Als Python detecteert dat het linkerargument geen andere verwijzingen heeft, roept het reallocaan om te proberen een kopie te vermijden door de tekenreeks op zijn plaats te verkleinen. Dit is niet iets waar je ooit op zou moeten vertrouwen, omdat het een implementatiedetail is en omdat als reallocde string vaak moet verplaatsen, de prestaties sowieso afnemen tot O(n^2).

Ik zal een voorbeeld laten zien van code uit de echte wereld die naïef op +=deze optimalisatie vertrouwde, maar die niet van toepassing was. De onderstaande code converteert een iterable van korte strings naar grotere brokken die in een bulk-API kunnen worden gebruikt.

def test_concat_chunk(seq, split_by):
    result = ['']
    for item in seq:
        if len(result[-1]) + len(item) > split_by: 
            result.append('')
        result[-1] += item
    return result

Deze code kan letterlijk uren draaien vanwege kwadratische tijdcomplexiteit. Hieronder staan alternatieven met voorgestelde datastructuren:

import io
def test_stringio_chunk(seq, split_by):
    def chunk():
        buf = io.StringIO()
        size = 0
        for item in seq:
            if size + len(item) <= split_by:
                size += buf.write(item)
            else:
                yield buf.getvalue()
                buf = io.StringIO()
                size = buf.write(item)
        if size:
            yield buf.getvalue()
    return list(chunk())
def test_join_chunk(seq, split_by):
    def chunk():
        buf = []
        size = 0
        for item in seq:
            if size + len(item) <= split_by:
                buf.append(item)
                size += len(item)
            else:
                yield ''.join(buf)                
                buf.clear()
                buf.append(item)
                size = len(item)
        if size:
            yield ''.join(buf)
    return list(chunk())

En een micro-benchmark:

import timeit
import random
import string
import matplotlib.pyplot as plt
line = ''.join(random.choices(
    string.ascii_uppercase + string.digits, k=512)) + '\n'
x = []
y_concat = []
y_stringio = []
y_join = []
n = 5
for i in range(1, 11):
    x.append(i)
    seq = [line] * (20 * 2 ** 20 // len(line))
    chunk_size = i * 2 ** 20
    y_concat.append(
        timeit.timeit(lambda: test_concat_chunk(seq, chunk_size), number=n) / n)
    y_stringio.append(
        timeit.timeit(lambda: test_stringio_chunk(seq, chunk_size), number=n) / n)
    y_join.append(
        timeit.timeit(lambda: test_join_chunk(seq, chunk_size), number=n) / n)
plt.plot(x, y_concat)
plt.plot(x, y_stringio)
plt.plot(x, y_join)
plt.legend(['concat', 'stringio', 'join'], loc='upper left')
plt.show()


Antwoord 9

Hoewel enigszins gedateerd, Code Like a Pythonista: Idiomatic Pythonbeveelt join()aan boven +in deze sectie. Net als PythonSpeedPerformanceTipsin de sectie op stringaaneenschakeling, met de volgende disclaimer:

De juistheid van deze sectie wordt betwist met betrekking tot later
versies van Python. In CPython 2.5 is de aaneenschakeling van tekenreeksen redelijk
snel, hoewel dit misschien niet op dezelfde manier van toepassing is op andere Python
implementaties. Zie ConcatenationTestCode voor een discussie.


Antwoord 10

U kunt op verschillende manieren doen.

str1 = "Hello"
str2 = "World"
str_list = ['Hello', 'World']
str_dict = {'str1': 'Hello', 'str2': 'World'}
# Concatenating With the + Operator
print(str1 + ' ' + str2)  # Hello World
# String Formatting with the % Operator
print("%s %s" % (str1, str2))  # Hello World
# String Formatting with the { } Operators with str.format()
print("{}{}".format(str1, str2))  # Hello World
print("{0}{1}".format(str1, str2))  # Hello World
print("{str1} {str2}".format(str1=str_dict['str1'], str2=str_dict['str2']))  # Hello World
print("{str1} {str2}".format(**str_dict))  # Hello World
# Going From a List to a String in Python With .join()
print(' '.join(str_list))  # Hello World
# Python f'strings --> 3.6 onwards
print(f"{str1} {str2}")  # Hello World

Ik heb deze kleine samenvatting door het volgen van artikelen.


Antwoord 11

U kunt gebruik maken van deze (meer efficiënt) ook. (https://softwareengineering.stackexchange.com/questions/304445/why -is-s-beter-dan-for-aaneenschakeling )

s += "%s" %(stringfromelsewhere)

Antwoord 12

mijn gebruik was iets anders. Ik moest een query maken waarin meer dan 20 velden dynamisch waren.
Ik volgde deze benadering van het gebruik van de opmaakmethode

query = "insert into {0}({1},{2},{3}) values({4}, {5}, {6})"
query.format('users','name','age','dna','suzan',1010,'nda')

dit was relatief eenvoudiger voor mij in plaats van + of andere manieren te gebruiken

Other episodes