it-swarm.com.de

Was ist der bevorzugte Weg, um einen String in Python zu verketten?

Da Pythons string nicht geändert werden kann, habe ich mich gefragt, wie eine Zeichenfolge effizienter verkettet werden kann.

Ich kann so schreiben:

s += stringfromelsewhere

oder so:

s = []
s.append(somestring)

later

s = ''.join(s)

Als ich diese Frage schrieb, fand ich einen guten Artikel zum Thema.

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

Aber es ist in Python 2.x., also würde sich die Frage in Python 3 ändern?

304
Max

Die beste Möglichkeit, eine Zeichenfolge an eine Zeichenfolgenvariable anzuhängen, ist die Verwendung von + oder +=. Dies liegt daran, dass es lesbar und schnell ist. Sie sind auch genauso schnell, welche Sie wählen, ist Geschmackssache, letztere ist die häufigste. Hier sind die Timings mit dem timeit Modul:

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

Diejenigen, die empfehlen, Listen zu haben und an sie anzuhängen und diese dann zu verbinden, tun dies, weil das Anhängen einer Zeichenfolge an eine Liste vermutlich sehr schnell ist, verglichen mit dem Erweitern einer Zeichenfolge. Und dies kann in einigen Fällen zutreffen. Hier ist zum Beispiel eine Million Anhänge einer Zeichenfolge mit einem Zeichen, zuerst an eine Zeichenfolge, dann an eine Liste:

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

OK, es stellte sich heraus, dass das Anhängen noch schneller war, obwohl die resultierende Zeichenfolge eine Million Zeichen lang war.

Versuchen wir nun, hunderttausend Mal eine Zeichenfolge mit tausend Zeichen anzufügen:

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

Die Endzeichenfolge ist daher ungefähr 100 MB lang. Das war ziemlich langsam, das Anhängen an eine Liste war viel schneller. Dass dieses Timing nicht das letzte a.join() enthält. Wie lange würde das dauern?

a.join(a):
0.43739795684814453

Oups. Es stellt sich heraus, dass das Anhängen/Verbinden auch in diesem Fall langsamer ist.

Woher kommt diese Empfehlung? Python 2?

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

Nun, das Anhängen/Verknüpfen ist geringfügig schneller, wenn Sie extrem lange Zeichenfolgen verwenden (was normalerweise nicht der Fall ist, was würden Sie für eine Zeichenfolge mit 100 MB haben?) in Erinnerung?)

Aber der wahre Drahtreifen ist Python 2.3. Wo ich dir nicht mal das Timing zeige, weil es so langsam ist, dass es noch nicht fertig ist. Diese Tests dauern plötzlich Minuten . Bis auf das Anhängen/Verbinden, das genauso schnell ist wie unter späteren Pythons.

Jep. Die Verkettung von Zeichenfolgen war in der Steinzeit in Python sehr langsam. In 2.4 ist dies jedoch nicht mehr der Fall (oder zumindest in Python 2.4.7), sodass die Empfehlung zur Verwendung von append/join im Jahr 2008 veraltet ist, als Python 2.3 nicht mehr aktualisiert wird. und Sie sollten aufgehört haben, es zu verwenden. :-)

(Update: Es stellte sich heraus, dass das Testen mit + und += für zwei Zeichenfolgen in Python 2.3 ebenfalls schneller ist Die Empfehlung, ''.join() zu verwenden, muss ein Missverständnis sein)

Dies ist jedoch CPython. Andere Implementierungen können andere Bedenken haben. Und dies ist nur ein weiterer Grund, warum vorzeitige Optimierung die Wurzel allen Übels ist. Verwenden Sie keine Technik, die "schneller" sein soll, es sei denn, Sie messen sie zuerst.

Daher ist die "beste" Version für die Verkettung von Zeichenfolgen die Verwendung von + oder + =. Und wenn sich herausstellt, dass das für Sie langsam ist, was ziemlich unwahrscheinlich ist, dann tun Sie etwas anderes.

Warum verwende ich also viel append/join in meinem Code? Denn manchmal ist es tatsächlich klarer. Vor allem, wenn alles, was Sie zusammenfügen möchten, durch Leerzeichen, Kommas oder Zeilenumbrüche getrennt werden soll.

379
Lennart Regebro

Wenn Sie viele Werte verketten, dann auch nicht. Das Anhängen einer Liste ist teuer. Sie können dafür StringIO verwenden. Vor allem, wenn Sie es über viele Operationen aufbauen.

from cStringIO import StringIO
# python3:  from io import StringIO

buf = StringIO()

buf.write('foo')
buf.write('foo')
buf.write('foo')

buf.getvalue()
# 'foofoofoo'

Wenn Sie bereits eine vollständige Liste von einem anderen Vorgang erhalten haben, verwenden Sie einfach ''.join(aList).

Aus dem python FAQ: Was ist der effizienteste Weg, um viele Zeichenfolgen miteinander zu verknüpfen?

str- und bytes-Objekte sind unveränderlich, daher ist das Zusammenfügen vieler Zeichenfolgen ineffizient, da bei jeder Verkettung ein neues Objekt erstellt wird. Im Allgemeinen sind die Gesamtlaufzeitkosten in der Gesamtlänge der Zeichenfolge quadratisch.

Um viele str-Objekte zu akkumulieren, wird empfohlen, sie in eine Liste einzufügen und am Ende str.join () aufzurufen:

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

(Eine weitere einigermaßen effiziente Sprache ist die Verwendung von io.StringIO.)

Um Objekte mit vielen Bytes zu akkumulieren, wird empfohlen, ein Bytearray-Objekt mithilfe der direkten Verkettung zu erweitern (der Operator + =):

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

Edit: Ich war albern und hatte die Ergebnisse rückwärts eingefügt, so dass es aussah, als wäre das Anhängen an eine Liste schneller als cStringIO. Ich habe auch Tests für bytearray/str concat sowie eine zweite Testrunde mit einer größeren Liste mit größeren Zeichenfolgen hinzugefügt. (Python 2.7.3)

Ipython-Testbeispiel für große Listen von 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
43
jdi

In Python> = 3.6 ist die neue f-Zeichenfolge eine effiziente Möglichkeit, eine Zeichenfolge zu verketten.

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

Wenn die Zeichenfolgen, die Sie verketten, Literale sind, verwenden Sie Zeichenfolgenliteralverkettung

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

Dies ist nützlich, wenn Sie einen Teil einer Zeichenfolge kommentieren möchten (wie oben) oder rohe Zeichenfolgen oder dreifache Anführungszeichen für einen Teil eines Literales, aber nicht für alle verwenden möchten.

Da dies auf der Syntaxebene geschieht, werden keine Verkettungsoperatoren verwendet.

8
droid

Die empfohlene Methode ist weiterhin das Anhängen und Verbinden.

8
MRAB

Die direkte Verkettung von Zeichenfolgen mit '+' ist die SCHLECHTESTE Methode zur Verkettung in Bezug auf Stabilität und Cross-Implementierung, da nicht alle Werte unterstützt werden. Der PEP8-Standard rät davon ab und empfiehlt die Verwendung von format (), join () und append () für die langfristige Verwendung.

6
badslacks

Obwohl etwas veraltet, empfiehlt Code Like a Pythonista: Idiomatic Pythonjoin() über +in diesem Abschnitt . Wie auch PythonSpeedPerformanceTips in seinem Abschnitt zu Zeichenfolgenverkettung mit dem folgenden Haftungsausschluss:

Die Genauigkeit dieses Abschnitts ist in Bezug auf spätere Versionen von Python umstritten. In CPython 2.5 ist die Verkettung von Zeichenfolgen relativ schnell, obwohl dies möglicherweise nicht für alle anderen Python -Implementierungen gilt. Eine Diskussion finden Sie unter ConcatenationTestCode.

6
Levon

Sie schreiben diese Funktion

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

Dann können Sie einfach anrufen, wohin Sie wollen

str_join('Pine')  # Returns : Pine
str_join('Pine', 'Apple')  # Returns : Pineapple
str_join('Pine', 'Apple', 3)  # Returns : Pineapple3
6
Shameem

Während @jdi Python erwähnt, empfiehlt die Dokumentation, str.join oder io.StringIO für die Verkettung von Zeichenfolgen zu verwenden. Und sagt, dass ein Entwickler quadratische Zeit von += in einer Schleife erwarten sollte, obwohl es eine Optimierung seit Python 2.4 gibt. Als diese Antwort sagt:

Wenn Python feststellt, dass das linke Argument keine anderen Verweise enthält, ruft es realloc auf, um zu versuchen, eine Kopie zu vermeiden, indem die Größe der Zeichenfolge geändert wird. Darauf sollten Sie sich nie verlassen, da es sich um ein Implementierungsdetail handelt und die Leistung ohnehin nachlässt, wenn realloc den String häufig verschieben muss.

Ich werde ein Beispiel für Code aus der realen Welt zeigen, der sich naiv auf += diese Optimierung stützte, aber nicht zutraf. Der folgende Code konvertiert eine Iteration von kurzen Zeichenfolgen in größere Blöcke, die in einer Bulk-API verwendet werden.

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

Dieser Code kann aufgrund der quadratischen Zeitkomplexität stundenlang literarisch ausgeführt werden. Nachfolgend finden Sie Alternativen mit vorgeschlagenen Datenstrukturen:

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())

Und ein Mikro-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()

micro-benchmark

5
saaj

mein Anwendungsfall war etwas anders. Ich musste eine Abfrage erstellen, in der mehr als 20 Felder dynamisch waren. Ich folgte diesem Ansatz der Verwendung der Formatmethode

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

dies war für mich vergleichsweise einfacher, als + oder andere Methoden zu verwenden

3
ishwar rimal

Sie können auf verschiedene Arten tun.

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

Ich habe diese kleine Zusammenfassung durch folgende Artikel erstellt.

2

Sie können dies auch (effizienter) nutzen. ( https://softwareengineering.stackexchange.com/questions/304445/why-is-s-better-than-for-concatenation )

s += "%s" %(stringfromelsewhere)
2
SuperNova