Chcę efektywnego sposobu dołączenia jednego ciągu do drugiego w Pythonie, innego niż poniższe.
var1 = "foo"
var2 = "bar"
var3 = var1 + var2
Czy jest jakaś dobra wbudowana metoda do użycia?
Chcę efektywnego sposobu dołączenia jednego ciągu do drugiego w Pythonie, innego niż poniższe.
var1 = "foo"
var2 = "bar"
var3 = var1 + var2
Czy jest jakaś dobra wbudowana metoda do użycia?
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
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):
void
PyBytes_ConcatAndDel(register PyObject **pv, register PyObject *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)
{
register PyObject *v;
register PyBytesObject *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 */
return 0;
}
Ł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.
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 +=
:
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)
str1 = "Hello"
str2 = "World"
newstr = " ".join((str1, str2))
Łą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.
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.
"<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())
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 +
.
Zasadniczo bez różnicy. Jedynym spójnym trendem jest to, że Python wydaje się zwalniać z każdą wersją ... :(
%%timeit
x = []
for i in range(100000000): # xrange on Python 2.7
x.append('a')
x = ''.join(x)
Python 2.7
1 pętla, najlepiej 3: 7,34 s na pętlę
Python 3.4
1 pętla, najlepiej 3: 7,99 s na pętlę
Python 3.5
1 pętla, najlepiej 3: 8,48 s na pętlę
Python 3.6
1 pętla, najlepiej 3: 9,93 s na pętlę
%%timeit
x = ''
for i in range(100000000): # xrange on Python 2.7
x += 'a'
Python 2.7 :
1 pętla, najlepiej 3: 7,41 s na pętlę
Python 3.4
1 pętla, najlepiej 3: 9,08 s na pętlę
Python 3.5
1 pętla, najlepiej 3: 8,82 s na pętli
Python 3.6
1 pętla, najlepiej 3: 9,24 s na pętlę
1.19 s
i 992 ms
odpowiednio na Python2.7
dołącz ciągi znaków z funkcją __add__
str = "Hello"
str2 = " World"
st = str.__add__(str2)
print(st)
Wynik
Hello World
str + str2
jest jeszcze krótszy.
a='foo'
b='baaz'
a.__add__(b)
out: 'foobaaz'
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)