TL; DR: Jeśli szukasz prostego sposobu dołączania ciągów, a nie zależy Ci na wydajności:"foo" + "bar" + str(3)
Andrew
Odpowiedzi:
609
Jeśli masz tylko jedno odniesienie do łańcucha i konkatenujesz inny łańcuch do końca, CPython teraz specjalne przypadki to i próbuje przedłużyć łańcuch w miejscu.
W rezultacie operacja jest amortyzowana przez O (n).
na przykład
s =""for i in range(n):
s+=str(i)
kiedyś było O (n ^ 2), ale teraz jest O (n).
Ze źródła (bytesobject.c):
voidPyBytes_ConcatAndDel(registerPyObject**pv,registerPyObject*w){PyBytes_Concat(pv, w);Py_XDECREF(w);}/* The following function breaks the notion that strings are immutable:
it changes the size of a string. We get away with this only if there
is only one module referencing the object. You can also think of it
as creating a new string object and destroying the old one, only
more efficiently. In any case, don't use this if the string may
already be known to some other part of the code...
Note that if there's not enough memory to resize the string, the original
string object at *pv is deallocated, *pv is set to NULL, an "out of
memory" exception is set, and -1 is returned. Else (on success) 0 is
returned, and the value in *pv may or may not be the same as on input.
As always, an extra byte is allocated for a trailing \0 byte (newsize
does *not* include that), and a trailing \0 byte is stored.
*/int_PyBytes_Resize(PyObject**pv,Py_ssize_t newsize){registerPyObject*v;registerPyBytesObject*sv;
v =*pv;if(!PyBytes_Check(v)||Py_REFCNT(v)!=1|| newsize <0){*pv =0;Py_DECREF(v);PyErr_BadInternalCall();return-1;}/* XXX UNREF/NEWREF interface should be more symmetrical */_Py_DEC_REFTOTAL;_Py_ForgetReference(v);*pv =(PyObject*)PyObject_REALLOC((char*)v,PyBytesObject_SIZE+ newsize);if(*pv == NULL){PyObject_Del(v);PyErr_NoMemory();return-1;}_Py_NewReference(*pv);
sv =(PyBytesObject*)*pv;Py_SIZE(sv)= newsize;
sv->ob_sval[newsize]='\0';
sv->ob_shash =-1;/* invalidate cached hash value */return0;}
Łatwo jest to zweryfikować empirycznie.
$ python -m timeit -s "s = ''" "dla i w xrange (10): s + = 'a'"
1000000 pętli, najlepiej 3: 1,85 usec na pętlę
$ python -m timeit -s "s = ''" "dla i w xrange (100): s + = 'a'"
10000 pętli, najlepiej 3: 16,8 użycia na pętlę
$ python -m timeit -s "s = ''" "dla i w xrange (1000): s + = 'a'"
10000 pętli, najlepiej 3: 158 usec na pętlę
$ python -m timeit -s "s = ''" "dla i w xrange (10000): s + = 'a'"
1000 pętli, najlepiej 3: 1,71 ms na pętlę
$ python -m timeit -s "s = ''" "dla i w xrange (100000): s + = 'a'"
10 pętli, najlepiej 3: 14,6 ms na pętlę
$ python -m timeit -s "s = ''" "dla i w xrange (1000000): s + = 'a'"
10 pętli, najlepiej 3: 173 ms na pętlę
Należy jednak pamiętać, że ta optymalizacja nie jest częścią specyfikacji Pythona. O ile wiem, jest to tylko implementacja cPython. Takie same testy empiryczne na przykład pypy lub jython mogą na przykład pokazać starszą wydajność O (n ** 2).
$ pypy -m timeit -s "s = ''" "dla i w xrange (10): s + = 'a'"
10000 pętli, najlepiej 3: 90,8 usec na pętlę
$ pypy -m timeit -s "s = ''" "dla i w xrange (100): s + = 'a'"
1000 pętli, najlepiej 3: 896 usec na pętlę
$ pypy -m timeit -s "s = ''" "dla i w xrange (1000): s + = 'a'"
100 pętli, najlepiej 3: 9,03 ms na pętlę
$ pypy -m timeit -s "s = ''" "dla i w xrange (10000): s + = 'a'"
10 pętli, najlepiej 3: 89,5 ms na pętlę
Jak dotąd tak dobrze, ale potem
$ pypy -m timeit -s "s = ''" "dla i w xrange (100000): s + = 'a'"
10 pętli, najlepiej 3: 12,8 s na pętlę
ouch jest nawet gorszy niż kwadratowy. Więc pypy robi coś, co działa dobrze z krótkimi łańcuchami, ale słabo sprawdza się w przypadku większych łańcuchów.
@ Steve, Nie. Jest co najmniej w wersji 2.6, a może nawet 2.5
John La Rooy,
8
Cytujesz tę PyString_ConcatAndDelfunkcję, ale dołączasz komentarz do niej _PyString_Resize. Ponadto komentarz tak naprawdę nie potwierdza twojego twierdzenia dotyczącego Big-O
Winston Ewert
3
gratuluję wykorzystania funkcji CPython, która sprawi, że kod będzie indeksował się w innych implementacjach. Zła rada.
Nie przedwcześnie optymalizuj. Jeśli nie masz powodu, aby sądzić, że istnieje wąskie gardło związane z łączeniem łańcuchów, po prostu trzymaj się +i +=:
s ='foo'
s +='bar'
s +='baz'
To powiedziawszy, jeśli dążysz do czegoś takiego jak StringBuilder Javy, kanoniczny idiom Pythona polega na dodawaniu elementów do listy, a następnie str.joinłączeniu ich na końcu:
l =[]
l.append('foo')
l.append('bar')
l.append('baz')
s =''.join(l)
Nie wiem, jakie są implikacje szybkości budowania ciągów jako list, a następnie ich .join (), ale uważam, że jest to na ogół najczystszy sposób. Odniosłem również duży sukces przy użyciu notacji% s w ciągu znaków dla silnika szablonów SQL, który napisałem.
richo
25
@Richo Korzystanie z .join jest bardziej wydajne. Powodem jest to, że ciągi Pythona są niezmienne, więc wielokrotne użycie s + = more spowoduje przydzielenie wielu kolejnych większych ciągów. .join wygeneruje końcowy ciąg za jednym razem z jego części składowych.
Ben
5
@Ben, nastąpiła znacząca poprawa w tym obszarze - patrz moja odpowiedź
Łączy str1 i str2 ze spacją jako separatory. Możesz też zrobić "".join(str1, str2, ...). str.join()wymaga iteracji, więc musisz umieścić ciągi na liście lub krotce.
Jest to mniej więcej tak wydajne, jak w przypadku wbudowanej metody.
przykro mi nie ma nic bardziej łatwiejsze do odczytania niż + ciąg (string), jak w pierwszym przykładzie, drugi Przykładem może być bardziej efektywne, ale nie bardziej czytelny
JqueryToAddNumbers
23
@ExceptionSlayer, ciąg + ciąg jest dość łatwy do naśladowania. Ale "<div class='" + className + "' id='" + generateUniqueId() + "'>" + message_text + "</div>"uważam, że mniej czytelny i podatny na błędy"<div class='{classname}' id='{id}'>{message_text}</div>".format(classname=class_name, message_text=message_text, id=generateUniqueId())
Winston Ewert
To wcale nie pomaga, gdy próbuję zrobić z grubsza odpowiednik, powiedzmy, ciąg znaków PHP / perl. = Verifydata () lub podobny.
Shadur
@Shadur, mam na myśli to, że powinieneś pomyśleć jeszcze raz, czy naprawdę chcesz zrobić coś równoważnego, czy może jest to zupełnie inne podejście?
Winston Ewert
1
W tym przypadku odpowiedź na to pytanie brzmi: „Nie, ponieważ to podejście nie obejmuje mojego przypadku użycia”
Shadur
11
Python 3.6 daje nam ciągi F , które są przyjemnością:
var1 ="foo"
var2 ="bar"
var3 = f"{var1}{var2}"print(var3)# prints foobar
Jeśli musisz wykonać wiele operacji dołączania, aby zbudować duży ciąg, możesz użyć StringIO lub cStringIO. Interfejs jest jak plik. tj .: możesz writedołączyć do niego tekst.
to naprawdę zależy od twojej aplikacji. Jeśli przeglądasz setki słów i chcesz dołączyć je wszystkie do listy, .join()lepiej. Ale jeśli układasz długie zdanie, lepiej jest użyć +=.
Kod jest fajny, ale pomogłoby to w dołączeniu wyjaśnienia. Dlaczego warto korzystać z tej metody zamiast innych odpowiedzi na tej stronie?
cgmb,
11
Używanie a.__add__(b)jest identyczne jak pisanie a+b. Podczas łączenia ciągów za pomocą +operatora, Python wywoła __add__metodę na ciąg po lewej stronie przechodzącej prawy boczny ciąg jako parametr.
"foo" + "bar" + str(3)
Odpowiedzi:
Jeśli masz tylko jedno odniesienie do łańcucha i konkatenujesz inny łańcuch do końca, CPython teraz specjalne przypadki to i próbuje przedłużyć łańcuch w miejscu.
W rezultacie operacja jest amortyzowana przez O (n).
na przykład
kiedyś było O (n ^ 2), ale teraz jest O (n).
Ze źródła (bytesobject.c):
Łatwo jest to zweryfikować empirycznie.
Należy jednak pamiętać, że ta optymalizacja nie jest częścią specyfikacji Pythona. O ile wiem, jest to tylko implementacja cPython. Takie same testy empiryczne na przykład pypy lub jython mogą na przykład pokazać starszą wydajność O (n ** 2).
Jak dotąd tak dobrze, ale potem
ouch jest nawet gorszy niż kwadratowy. Więc pypy robi coś, co działa dobrze z krótkimi łańcuchami, ale słabo sprawdza się w przypadku większych łańcuchów.
źródło
PyString_ConcatAndDel
funkcję, ale dołączasz komentarz do niej_PyString_Resize
. Ponadto komentarz tak naprawdę nie potwierdza twojego twierdzenia dotyczącego Big-O"".join(str_a, str_b)
Nie przedwcześnie optymalizuj. Jeśli nie masz powodu, aby sądzić, że istnieje wąskie gardło związane z łączeniem łańcuchów, po prostu trzymaj się
+
i+=
:To powiedziawszy, jeśli dążysz do czegoś takiego jak StringBuilder Javy, kanoniczny idiom Pythona polega na dodawaniu elementów do listy, a następnie
str.join
łączeniu ich na końcu:źródło
Łączy str1 i str2 ze spacją jako separatory. Możesz też zrobić
"".join(str1, str2, ...)
.str.join()
wymaga iteracji, więc musisz umieścić ciągi na liście lub krotce.Jest to mniej więcej tak wydajne, jak w przypadku wbudowanej metody.
źródło
Nie rób
Oznacza to, że w większości przypadków lepiej jest generować cały ciąg za jednym razem, niż dołączać do istniejącego ciągu.
Na przykład nie rób:
obj1.name + ":" + str(obj1.count)
Zamiast tego: użyj
"%s:%d" % (obj1.name, obj1.count)
To będzie łatwiejsze do odczytania i bardziej wydajne.
źródło
"<div class='" + className + "' id='" + generateUniqueId() + "'>" + message_text + "</div>"
uważam, że mniej czytelny i podatny na błędy"<div class='{classname}' id='{id}'>{message_text}</div>".format(classname=class_name, message_text=message_text, id=generateUniqueId())
Python 3.6 daje nam ciągi F , które są przyjemnością:
W nawiasach klamrowych możesz zrobić wszystko
źródło
Jeśli musisz wykonać wiele operacji dołączania, aby zbudować duży ciąg, możesz użyć StringIO lub cStringIO. Interfejs jest jak plik. tj .: możesz
write
dołączyć do niego tekst.Jeśli dodajesz tylko dwa ciągi, użyj po prostu
+
.źródło
to naprawdę zależy od twojej aplikacji. Jeśli przeglądasz setki słów i chcesz dołączyć je wszystkie do listy,
.join()
lepiej. Ale jeśli układasz długie zdanie, lepiej jest użyć+=
.źródło
Zasadniczo bez różnicy. Jedynym spójnym trendem jest to, że Python wydaje się zwalniać z każdą wersją ... :(
Lista
Python 2.7
Python 3.4
Python 3.5
Python 3.6
Strunowy
Python 2.7 :
Python 3.4
Python 3.5
Python 3.6
źródło
1.19 s
i992 ms
odpowiednio na Python2.7dołącz ciągi znaków z funkcją __add__
Wynik
źródło
str + str2
jest jeszcze krótszy.źródło
a.__add__(b)
jest identyczne jak pisaniea+b
. Podczas łączenia ciągów za pomocą+
operatora, Python wywoła__add__
metodę na ciąg po lewej stronie przechodzącej prawy boczny ciąg jako parametr.