Bagaimana cara menambahkan satu string ke string lain dengan Python?

594

Saya ingin cara yang efisien untuk menambahkan satu string ke yang lain dengan Python, selain yang berikut.

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

Apakah ada metode bawaan yang baik untuk digunakan?

pengguna469652
sumber
8
TL; DR: Jika Anda hanya mencari cara sederhana untuk menambahkan string, dan Anda tidak peduli dengan efisiensi:"foo" + "bar" + str(3)
Andrew

Jawaban:

609

Jika Anda hanya memiliki satu referensi ke sebuah string dan Anda merangkai string yang lain sampai akhir, CPython sekarang memberikan kasus khusus ini dan mencoba untuk memperpanjang string di tempatnya.

Hasil akhirnya adalah bahwa operasi tersebut diamortisasi O (n).

misalnya

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

dulu O (n ^ 2), tapi sekarang O (n).

Dari sumber (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;
}

Cukup mudah untuk memverifikasi secara empiris.

$ python -m timeit -s "s = ''" "untuk saya di xrange (10): s + = 'a'"
10.00000 loop, terbaik 3: 1,85 USD per loop
$ python -m timeit -s "s = ''" "untuk saya di xrange (100): s + = 'a'"
10000 loop, terbaik 3: 16,8 usec per loop
$ python -m timeit -s "s = ''" "untuk saya di xrange (1000): s + = 'a'"
10.000 loop, terbaik 3: 158 usec per loop
$ python -m timeit -s "s = ''" "untuk saya di xrange (10000): s + = 'a'"
1000 loop, terbaik 3: 1,71 msec per loop
$ python -m timeit -s "s = ''" "untuk saya di xrange (100000): s + = 'a'"
10 loop, terbaik 3: 14,6 msec per loop
$ python -m timeit -s "s = ''" "untuk saya di xrange (1000000): s + = 'a'"
10 loop, terbaik 3: 173 msec per loop

Namun penting untuk dicatat bahwa optimasi ini bukan bagian dari spesifikasi Python. Itu hanya dalam implementasi cPython sejauh yang saya tahu. Pengujian empiris yang sama pada pypy atau jython misalnya dapat menunjukkan kinerja O (n ** 2) yang lebih lama.

$ pypy -m timeit -s "s = ''" "untuk saya di xrange (10): s + = 'a'"
10000 loop, terbaik dari 3: 90,8 USD per loop
$ pypy -m timeit -s "s = ''" "untuk saya di xrange (100): s + = 'a'"
1000 loop, terbaik 3: 896 usec per loop
$ pypy -m timeit -s "s = ''" "untuk saya di xrange (1000): s + = 'a'"
100 loop, terbaik 3: 9,03 msec per loop
$ pypy -m timeit -s "s = ''" "untuk saya di xrange (10000): s + = 'a'"
10 loop, terbaik 3: 89,5 msec per loop

Sejauh ini bagus, tapi kemudian,

$ pypy -m timeit -s "s = ''" "untuk saya di xrange (100000): s + = 'a'"
10 loop, terbaik 3: 12,8 detik per loop

Aduh bahkan lebih buruk dari kuadrat. Jadi pypy melakukan sesuatu yang bekerja dengan baik dengan string pendek, tetapi berkinerja buruk untuk string yang lebih besar.

John La Rooy
sumber
14
Menarik. Dengan "sekarang", maksud Anda Python 3.x?
Steve Tjoa
10
@Steve, Tidak. Ini setidaknya di 2,6 mungkin bahkan 2,5
John La Rooy
8
Anda telah mengutip PyString_ConcatAndDelfungsinya tetapi menyertakan komentar untuk _PyString_Resize. Juga, komentar itu tidak benar-benar membuktikan klaim Anda tentang Big-O
Winston Ewert
3
selamat atas eksploitasi fitur CPython yang akan membuat kode merayapi implementasi lainnya. Saran yang buruk.
Jean-François Fabre
4
JANGAN gunakan ini. Pep8 menyatakan secara eksplisit: Kode harus ditulis dengan cara yang tidak merugikan implementasi Python lainnya (PyPy, Jython, IronPython, Cython, Psyco, dan semacamnya , kemudian memberikan contoh khusus ini sebagai sesuatu yang harus dihindari karena sangat rapuh. Penggunaan yang lebih baik"".join(str_a, str_b)
Eraw
287

Jangan mengoptimalkan secara prematur. Jika Anda tidak memiliki alasan untuk percaya ada bottleneck cepat yang disebabkan oleh penggabungan string maka tetaplah dengan +dan +=:

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

Yang mengatakan, jika Anda bertujuan untuk sesuatu seperti StringBuilder Java, idiom Python kanonik adalah menambahkan item ke daftar dan kemudian gunakan str.joinuntuk menggabungkan semuanya pada akhirnya:

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

s = ''.join(l)
John Kugelman
sumber
Saya tidak tahu apa implikasi kecepatan membangun string Anda sebagai daftar dan kemudian bergabung dengan mereka, tetapi saya menemukan itu umumnya cara terbersih. Saya juga telah sukses besar dengan menggunakan notasi% s dalam sebuah string untuk mesin templating SQL yang saya tulis.
richo
25
@Richo Menggunakan .join lebih efisien. Alasannya adalah bahwa string Python tidak dapat diubah, jadi berulang kali menggunakan s + = lebih akan mengalokasikan banyak string yang lebih besar secara berturut-turut. .join akan menghasilkan string terakhir dalam sekali jalan dari bagian-bagian penyusunnya.
Ben
5
@ Ben, ada peningkatan signifikan di bidang ini - lihat jawaban saya
John La Rooy
41
str1 = "Hello"
str2 = "World"
newstr = " ".join((str1, str2))

Itu bergabung dengan str1 dan str2 dengan spasi sebagai pemisah. Anda juga bisa melakukannya "".join(str1, str2, ...). str.join()Dibutuhkan iterable, jadi Anda harus meletakkan string dalam daftar atau tuple.

Itu tentang seefisien yang didapat untuk metode builtin.

Rafe Kettler
sumber
Apa yang terjadi, jika str1 kosong? Apakah spasi akan ditetapkan?
Jürgen K.
38

Jangan.

Artinya, untuk sebagian besar kasus, Anda lebih baik membuat seluruh string dalam sekali jalan daripada menambahkan ke string yang ada.

Misalnya, jangan lakukan: obj1.name + ":" + str(obj1.count)

Sebaliknya: gunakan "%s:%d" % (obj1.name, obj1.count)

Itu akan lebih mudah dibaca dan lebih efisien.

Winston Ewert
sumber
54
maaf tidak ada yang lebih mudah dibaca daripada (string + string) seperti contoh pertama, contoh kedua mungkin lebih efisien, tetapi tidak lebih mudah dibaca
JqueryToAddNumbers
23
@ExceptionSlayer, string + string cukup mudah diikuti. Tapi "<div class='" + className + "' id='" + generateUniqueId() + "'>" + message_text + "</div>", saya menemukan itu kurang mudah dibaca dan rawan kesalahan kemudian"<div class='{classname}' id='{id}'>{message_text}</div>".format(classname=class_name, message_text=message_text, id=generateUniqueId())
Winston Ewert
Ini tidak membantu sama sekali ketika apa yang saya coba lakukan adalah setara dengan, katakanlah, "string. = Verifydata ()" PHP / perl atau sejenisnya.
Shadur
@ Safad, maksud saya adalah Anda harus berpikir lagi, apakah Anda benar-benar ingin melakukan sesuatu yang setara, atau apakah pendekatan yang sama sekali berbeda lebih baik?
Winston Ewert
1
Dan dalam hal ini jawaban untuk pertanyaan itu adalah "Tidak, karena pendekatan itu tidak mencakup kasus penggunaan saya"
Shadur
11

Python 3.6 memberi kita f-string , yang menyenangkan:

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

Anda dapat melakukan hampir semua hal di dalam kurung kurawal

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

Jika Anda perlu melakukan banyak operasi tambahan untuk membangun string besar, Anda dapat menggunakan StringIO atau cStringIO. Antarmuka seperti file. yaitu: kamuwrite menambahkan teks ke dalamnya.

Jika Anda hanya menambahkan dua string maka gunakan saja +.

Laurence Gonsalves
sumber
9

itu sangat tergantung pada aplikasi Anda. Jika Anda menggunakan ratusan kata dan ingin menambahkan semuanya ke dalam daftar, .join()lebih baik. Tetapi jika Anda menyusun kalimat yang panjang, Anda lebih baik menggunakannya +=.

Ramy
sumber
5

Pada dasarnya tidak ada perbedaan. Satu-satunya tren yang konsisten adalah bahwa Python tampaknya semakin lambat dengan setiap versi ... :(


Daftar

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

Python 2.7

1 loop, terbaik 3: 7.34 s per loop

Python 3.4

1 loop, terbaik 3: 7.99 s per loop

Python 3.5

1 loop, terbaik 3: 8,48 s per loop

Python 3.6

1 loop, terbaik 3: 9,93 detik per loop


Tali

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

Python 2.7 :

1 loop, terbaik 3: 7.41 s per loop

Python 3.4

1 loop, terbaik 3: 9,08 s per loop

Python 3.5

1 loop, terbaik 3: 8,82 detik per loop

Python 3.6

1 loop, terbaik 3: 9,24 s per loop

ostrokach
sumber
2
Saya kira itu tergantung. Saya mendapatkan 1.19 sdan 992 msmasing - masing di Python2.7
John La Rooy
5

tambahkan string dengan fungsi __add__

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

Keluaran

Hello World
Sai Gopi N
sumber
4
str + str2masih lebih pendek.
Nik O'Lai
2
a='foo'
b='baaz'

a.__add__(b)

out: 'foobaaz'
Rahul Shrivastava
sumber
1
Kode itu bagus, tetapi akan membantu jika ada penjelasan yang menyertainya. Mengapa menggunakan metode ini daripada jawaban lain di halaman ini?
cgmb
11
Penggunaan a.__add__(b)identik dengan menulis a+b. Saat Anda menggabungkan string menggunakan +operator, Python akan memanggil __add__metode pada string di sisi kiri melewati string sisi kanan sebagai parameter.
Addie