Ik wil een efficiënte manier om de ene string aan de andere toe te voegen in Python, behalve de volgende.
var1 = "foo"
var2 = "bar"
var3 = var1 + var2
Is er een goede ingebouwde methode om te gebruiken?
Antwoord 1, autoriteit 100%
Als je maar één verwijzing naar een tekenreeks hebt en je voegt een andere tekenreeks aan het einde toe, dan maakt CPython dit nu speciaal en probeert de tekenreeks op zijn plaats uit te breiden.
Het eindresultaat is dat de operatie O(n) wordt afgeschreven.
bijv.
s = ""
for i in range(n):
s+=str(i)
was vroeger O(n^2), maar nu is het O(n).
Van de bron (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;
}
Het is eenvoudig genoeg om empirisch te verifiëren.
$ python -m timeit -s"s=''" "voor i in xrange(10):s+='a'" 1000000 lussen, beste van 3: 1,85 usec per lus $ python -m timeit -s"s=''" "voor i in xrange(100):s+='a'" 10000 loops, beste van 3: 16,8 usec per loop $ python -m timeit -s"s=''" "voor i in xrange(1000):s+='a'" 10000 loops, beste van 3: 158 usec per loop $ python -m timeit -s"s=''" "voor i in xrange (10000):s+='a'" 1000 lussen, beste van 3: 1,71 msec per lus $ python -m timeit -s"s=''" "voor i in xrange (100000):s+='a'" 10 lussen, beste van 3: 14,6 msec per lus $ python -m timeit -s"s=''" "voor i in xrange(1000000):s+='a'" 10 lussen, beste van 3: 173 msec per lus
Het is echter belangrijk op te merken dat deze optimalisatie geen deel uitmaakt van de Python-specificatie. Het is alleen in de cPython-implementatie voor zover ik weet. Dezelfde empirische tests op pypy of jython kunnen bijvoorbeeld de oudere O(n**2)-prestaties aantonen.
$ pypy -m timeit -s"s=''" "voor i in xrange(10):s+='a'" 10000 lussen, beste van 3: 90,8 usec per lus $ pypy -m timeit -s"s=''" "voor i in xrange(100):s+='a'" 1000 loops, beste van 3: 896 usec per loop $ pypy -m timeit -s"s=''" "voor i in xrange(1000):s+='a'" 100 lussen, beste van 3: 9,03 msec per lus $ pypy -m timeit -s"s=''" "voor i in xrange (10000):s+='a'" 10 lussen, beste van 3: 89,5 msec per lus
Tot nu toe goed, maar dan,
$ pypy -m timeit -s"s=''" "voor i in xrange (100000):s+='a'" 10 lussen, beste van 3: 12,8 sec per lus
nog erger dan kwadratisch. Dus pypy doet iets dat goed werkt met korte snaren, maar slecht presteert voor grotere snaren.
Antwoord 2, autoriteit 46%
Niet voortijdig optimaliseren. Als je geen reden hebt om aan te nemen dat er een snelheidsprobleem is dat wordt veroorzaakt door aaneenschakeling van tekenreeksen, blijf dan gewoon bij +
en +=
:
s = 'foo'
s += 'bar'
s += 'baz'
Dat gezegd hebbende, als je iets als Java’s StringBuilder nastreeft, is het canonieke Python-idioom om items aan een lijst toe te voegen en vervolgens str.join
te gebruiken om ze allemaal aan het einde samen te voegen:
l = []
l.append('foo')
l.append('bar')
l.append('baz')
s = ''.join(l)
Antwoord 3, autoriteit 7%
str1 = "Hello"
str2 = "World"
newstr = " ".join((str1, str2))
Dat verbindt str1 en str2 met een spatie als scheidingsteken. Je kunt ook "".join(str1, str2, ...)
doen. str.join()
neemt een iterable, dus je zou de strings in een lijst of een tuple moeten zetten.
Dat is ongeveer net zo efficiënt als mogelijk is voor een ingebouwde methode.
Antwoord 4, autoriteit 6%
Niet doen.
Dat wil zeggen, in de meeste gevallen kunt u beter de hele reeks in één keer genereren in plaats van deze aan een bestaande reeks toe te voegen.
Doe bijvoorbeeld niet: obj1.name + ":" + str(obj1.count)
In plaats daarvan: gebruik "%s:%d" % (obj1.name, obj1.count)
Dat is gemakkelijker te lezen en efficiënter.
Antwoord 5, autoriteit 2%
Python 3.6 geeft ons f-strings, die een genot:
var1 = "foo"
var2 = "bar"
var3 = f"{var1}{var2}"
print(var3) # prints foobar
Je kunt bijna alles doen binnen de accolades
print(f"1 + 1 == {1 + 1}") # prints 1 + 1 == 2
Antwoord 6, autoriteit 2%
Als u veel toevoegbewerkingen moet uitvoeren om een grote tekenreeks te maken, kunt u StringIO of cStringIO. De interface is als een bestand. dat wil zeggen: u write
om er tekst aan toe te voegen.
Als je slechts twee strings toevoegt, gebruik dan gewoon +
.
Antwoord 7
het hangt echt af van uw toepassing. Als je honderden woorden doorloopt en ze allemaal aan een lijst wilt toevoegen, is .join()
beter. Maar als je een lange zin maakt, kun je beter +=
gebruiken.
Antwoord 8
In principe geen verschil. De enige consistente trend is dat Python met elke versie langzamer lijkt te worden… 🙁
Lijst
%%timeit
x = []
for i in range(100000000): # xrange on Python 2.7
x.append('a')
x = ''.join(x)
Python 2.7
1 lus, beste van 3: 7,34 s per lus
Python 3.4
1 lus, beste van 3: 7,99 s per lus
Python 3.5
1 lus, beste van 3: 8,48 s per lus
Python 3.6
1 lus, beste van 3: 9,93 s per lus
String
%%timeit
x = ''
for i in range(100000000): # xrange on Python 2.7
x += 'a'
Python 2.7:
1 lus, beste van 3: 7,41 s per lus
Python 3.4
1 lus, beste van 3: 9,08 s per lus
Python 3.5
1 lus, beste van 3: 8,82 s per lus
Python 3.6
1 lus, beste van 3: 9,24 s per lus
Antwoord 9
strings toevoegen met de functie add
str1 = "Hello"
str2 = " World"
str3 = str.__add__(str2)
print(str3)
Uitvoer
Hello World
Antwoord 10
a='foo'
b='baaz'
a.__add__(b)
out: 'foobaaz'