Python'da bir dizeyi diğerine nasıl eklerim?


594

Aşağıdakiler dışında Python başka bir dize eklemek için etkili bir yol istiyorum.

var1 = "foo"
var2 = "bar"
var3 = var1 + var2

Kullanılacak iyi bir yerleşik yöntem var mı?


8
TL; DR: Dizeleri eklemenin basit bir yolunu arıyorsanız ve verimliliği umursamıyorsanız:"foo" + "bar" + str(3)
Andrew

Yanıtlar:


609

Bir dizeye yalnızca bir başvurunuz varsa ve sonuna kadar başka bir dizeyi birleştirirseniz, CPython şimdi bunu özel olarak ele alır ve dizeyi yerinde genişletmeye çalışır.

Nihai sonuç, operasyonun O (n) amortismana tabi tutulmasıdır.

Örneğin

s = ""
for i in range(n):
    s+=str(i)

eskiden O (n ^ 2) idi, ama şimdi O (n).

Kaynaktan (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;
}

Ampirik olarak doğrulamak yeterince kolaydır.

$ python -m timeit -s "s = ''" "xrange (10) 'da: s + =' a '"
1000000 döngü, döngü başına en iyi 3: 1,85 usec
$ python -m timeit -s "s = ''" "xrange'de (100): s + = 'a'"
10000 döngü, döngü başına en iyi 3: 16,8 usec
$ python -m timeit -s "s = ''" "xrange (1000) 'de: s + =' a '"
10000 döngü, en iyi döngü başına 3: 158 usec
$ python -m timeit -s "s = ''" "xrange'de (10000): s + = 'a'"
1000 döngü, döngü başına en iyi 3: 1,71 msn
xrange'de (100000) i için $ python -m timeit -s "s = ''" ": s + = 'a'"
10 döngü, döngü başına en iyi 3: 14,6 ms
$ python -m timeit -s "s = ''" "xrange'de (1000000): s + = 'a'"
10 döngü, döngü başına en iyi 3: 173 msn

Ancak bu optimizasyonun Python spesifikasyonunun bir parçası olmadığını belirtmek önemlidir . Bildiğim kadarıyla sadece cPython uygulamasında. Örneğin, pypy veya jython üzerinde yapılan aynı ampirik testler eski O (n ** 2) performansını gösterebilir.

$ pypy -m timeit -s "s = ''" "xrange'deki (10): s + = 'a'"
10000 döngü, döngü başına en iyi 3: 90,8 usec
$ pypy -m timeit -s "s = ''" "xrange (100) 'de: s + =' a '"
1000 döngü, döngü başına en iyi 3: 896 usec
$ pypy -m timeit -s "s = ''" "xrange (1000) 'de: s + =' a '"
100 döngü, en iyi 3: döngü başına 9,03 ms
$ pypy -m timeit -s "s = ''" "xrange'de (10000): s + = 'a'"
10 döngü, döngü başına en iyi 3: 89,5 ms

Şimdiye kadar çok iyi, ama sonra,

$ pypy -m timeit -s "s = ''" "xrange'de (100000): s + = 'a'"
10 döngü, döngü başına en iyi 3: 12,8 sn

ikinci dereceden bile daha kötü. Yani pypy kısa dizelerle iyi çalışan bir şey yapıyor, ancak daha büyük dizeler için kötü performans gösteriyor.


14
İlginç. "Şimdi" ile Python 3.x demek istediniz?
Steve Tjoa

10
@Steve, Hayır. En azından 2.6'da belki 2.5 bile
John La Rooy

8
PyString_ConcatAndDelİşlevi alıntıladınız ancak yorumunu eklediniz _PyString_Resize. Ayrıca, yorum gerçekten Big-O ile ilgili iddianızı oluşturmuyor
Winston Ewert

3
kodun diğer uygulamalarda taranmasını sağlayacak bir CPython özelliğinden yararlandığınız için tebrikler. Kötü tavsiye.
Jean-François Fabre

4
Bunu KULLANMAYIN. Pep8 açık bir şekilde ifade eder : Kod, Python'un diğer uygulamalarını (PyPy, Jython, IronPython, Cython, Psyco ve benzeri) dezavantajlı olmayacak şekilde yazılmalıdır , daha sonra bu özel örneği çok kırılgan olduğu için kaçınılması gereken bir şey olarak verir."".join(str_a, str_b)
Eraw

287

Zamanından önce optimize etmeyin. Dize birleşmelerinin neden olduğu bir hız darboğazı olduğuna inanmak için bir nedeniniz yoksa, sadece +ve ile sopa +=:

s  = 'foo'
s += 'bar'
s += 'baz'

Bununla birlikte, Java'nın StringBuilder'ı gibi bir şey hedefliyorsanız, standart Python deyimi bir listeye öğe eklemek ve daha sonra str.joinhepsini birleştirmek için kullanmaktır :

l = []
l.append('foo')
l.append('bar')
l.append('baz')

s = ''.join(l)

Dizelerinizi liste olarak oluşturmanın ve sonra .join () ing'inin hız sonuçlarının ne olduğunu bilmiyorum, ancak genellikle en temiz yol olduğunu düşünüyorum. Ben de yazdım bir SQL şablon motoru için bir dize içinde% s gösterimi kullanarak büyük başarılar yaşadım.
richo

25
@Richo .join kullanımı daha verimlidir. Bunun nedeni, Python dizelerinin değişmez olmasıdır, bu nedenle tekrar tekrar s + = more kullanılması art arda daha büyük dizeler tahsis edecektir. .join, nihai ipi kurucu parçalarından tek seferde üretecektir.
Ben

5
@Ben, bu alanda önemli bir gelişme oldu - cevabımı görün
John La Rooy

41
str1 = "Hello"
str2 = "World"
newstr = " ".join((str1, str2))

Bu str1 ve str2'yi ayırıcılar olarak boşlukla birleştirir. Siz de yapabilirsiniz "".join(str1, str2, ...). str.join()bir yinelenebilir alır, bu nedenle dizeleri bir listeye veya bir tuple koymak zorunda kalırsınız.

Bu yerleşik bir yöntem için gereken kadar verimlidir.


Str1 empy ise ne olur? Boşluk ayarlanacak mı?
Jürgen K.

38

Yapma.

Diğer bir deyişle, çoğu durumda varolan bir dizeye eklemek yerine tüm dizeyi tek seferde oluşturmak daha iyidir.

Örneğin, şunları yapmayın: obj1.name + ":" + str(obj1.count)

Bunun yerine: kullanın "%s:%d" % (obj1.name, obj1.count)

Bunu okumak daha kolay ve daha verimli olacak.


54
üzgünüm, ilk örnek gibi (dize + dize) okumak daha kolay bir şey yok, ikinci örnek daha verimli, ama daha okunabilir
olmayabilir

23
@ExceptionSlayer, string + string'i takip etmek oldukça kolaydır. Ancak "<div class='" + className + "' id='" + generateUniqueId() + "'>" + message_text + "</div>", o zaman daha az okunabilir ve hataya yatkın buluyorum"<div class='{classname}' id='{id}'>{message_text}</div>".format(classname=class_name, message_text=message_text, id=generateUniqueId())
Winston Ewert

PHP / perl'in "string. = Verifydata ()" ya da benzerinin kabaca eşdeğerini yapmaya çalıştığımda bu hiç yardımcı olmaz.
Shadur

@Shadur, benim açımdan tekrar düşünmeniz gerektiği, gerçekten eşdeğer bir şey yapmak mı istiyorsunuz yoksa tamamen farklı bir yaklaşım mı daha iyi?
Winston Ewert

1
Ve bu durumda bu sorunun cevabı "Hayır, çünkü bu yaklaşım benim kullanım durumumu
kapsamıyor

11

Python 3.6 bize bir zevk olan f-stringleri verir :

var1 = "foo"
var2 = "bar"
var3 = f"{var1}{var2}"
print(var3)                       # prints foobar

Kıvırcık parantez içinde çoğu şeyi yapabilirsiniz

print(f"1 + 1 == {1 + 1}")        # prints 1 + 1 == 2

10

Büyük bir dize oluşturmak için birçok ekleme işlemi yapmanız gerekiyorsa, StringIO veya cStringIO kullanabilirsiniz . Arayüz bir dosya gibidir. ie: writemetne metin ekleyebilirsiniz.

Sadece iki dize ekliyorsanız kullanın +.


9

gerçekten uygulamanıza bağlıdır. Yüzlerce kelime arasında dolaşıyorsanız ve hepsini bir listeye eklemek istiyorsanız .join(), daha iyidir. Ancak uzun bir cümle kurarsanız, kullanmanız daha iyi olur +=.


5

Temel olarak, hiçbir fark yok. Tek tutarlı trend, Python'un her sürümde yavaşladığı görülüyor ... :(


Liste

%%timeit
x = []
for i in range(100000000):  # xrange on Python 2.7
    x.append('a')
x = ''.join(x)

Python 2.7

1 döngü, en iyi 3: döngü başına 7,34 s

Python 3.4

1 döngü, en iyi 3: döngü başına 7,99 s

Python 3.5

1 loop, en iyisi 3: loop başına 8.48 s

Python 3.6

1 döngü, en iyi 3: döngü başına 9,93 s


sicim

%%timeit
x = ''
for i in range(100000000):  # xrange on Python 2.7
    x += 'a'

Python 2.7 :

1 loop, en iyisi 3: loop başına 7.41 s

Python 3.4

1 döngü, en iyisi 3: döngü başına 9,08 s

Python 3.5

1 loop, en iyisi 3: loop başına 8.82 s

Python 3.6

1 loop, en iyisi 3: loop başına 9.24 s


2
Sanırım buna bağlı. Ben alıyorum 1.19 sve 992 mssırasıyla Python2.7
John La Rooy

5

__add__ işlevi ile dize ekleme

str = "Hello"
str2 = " World"
st = str.__add__(str2)
print(st)

Çıktı

Hello World

4
str + str2hala daha kısadır.
Nik O'Lai

2
a='foo'
b='baaz'

a.__add__(b)

out: 'foobaaz'

1
Kod güzel, ama eşlik eden bir açıklama yapmak yardımcı olacaktır. Neden bu sayfadaki diğer cevaplar yerine bu yöntemi kullanmalısınız?
cgmb

11
Kullanmak a.__add__(b)yazma ile aynıdır a+b. Dizeleri +işleç kullanarak birleştirdiğinizde , Python __add__sol taraftaki dizede yöntemi sağ taraftaki dizeyi parametre olarak geçirerek çağırır .
Addie
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.