Ada alasan untuk tidak menggunakan '+' untuk menggabungkan dua string?


124

Antipattern yang umum dalam Python adalah menggabungkan urutan string menggunakan +dalam satu lingkaran. Ini buruk karena penerjemah Python harus membuat objek string baru untuk setiap iterasi, dan akhirnya memakan waktu kuadrat. (Versi terbaru CPython tampaknya dapat mengoptimalkan ini dalam beberapa kasus, tetapi implementasi lain tidak dapat melakukannya, sehingga pemrogram tidak disarankan untuk mengandalkan ini.) ''.joinAdalah cara yang tepat untuk melakukan ini.

Namun, saya pernah mendengarnya mengatakan ( termasuk di sini di Stack Overflow ) bahwa Anda tidak boleh pernah menggunakan +untuk penggabungan string, tetapi selalu menggunakan ''.joinatau format string. Saya tidak mengerti mengapa ini terjadi jika Anda hanya menggabungkan dua string. Jika pemahaman saya benar, tidak perlu waktu kuadrat, dan menurut saya a + blebih bersih dan mudah dibaca daripada ''.join((a, b))atau '%s%s' % (a, b).

Apakah praktik yang baik menggunakan +menggabungkan dua string? Atau apakah ada masalah yang tidak saya sadari?


Ini lebih rapi dan Anda memiliki kontrol lebih untuk tidak melakukan penggabungan. TAPI sedikit lebih lambat, pertukaran bashing: P
Jakob Bowyer

Apakah Anda mengatakan +lebih cepat atau lebih lambat? Dan mengapa?
Taymon

1
+ lebih cepat, In [2]: %timeit "a"*80 + "b"*80 1000000 loops, best of 3: 356 ns per loop In [3]: %timeit "%s%s" % ("a"*80, "b"*80) 1000000 loops, best of 3: 907 ns per loop
Jakob Bowyer

4
In [3]: %timeit "%s%s" % (a, b) 1000000 loops, best of 3: 590 ns per loop In [4]: %timeit a + b 10000000 loops, best of 3: 147 ns per loop
Jakob Bowyer

1
@ JakobBowyer dan lainnya: Argumen "penggabungan string buruk" hampir tidak ada hubungannya dengan kecepatan, tetapi memanfaatkan konversi tipe otomatis dengan __str__. Lihat jawaban saya untuk contoh.
Izkata

Jawaban:


120

Tidak ada salahnya menggabungkan dua string dengan +. Memang lebih mudah dibaca daripada ''.join([a, b]).

Anda benar meskipun menggabungkan lebih dari 2 string dengan +adalah operasi O (n ^ 2) (dibandingkan dengan O (n) untuk join) dan dengan demikian menjadi tidak efisien. Namun ini tidak ada hubungannya dengan menggunakan loop. Even a + b + c + ...adalah O (n ^ 2), alasannya adalah bahwa setiap rangkaian menghasilkan string baru.

CPython2.4 dan yang lebih baru mencoba menguranginya, tetapi tetap disarankan untuk digunakan joinsaat menggabungkan lebih dari 2 string.


5
@Mutant: .joinmengambil iterable, sehingga kedua .join([a,b])dan .join((a,b))berlaku.
terlantar

1
Petunjuk waktu yang menarik untuk menggunakan +atau +=dalam jawaban yang diterima (dari 2013) di stackoverflow.com/a/12171382/378826 (dari Lennart Regebro) bahkan untuk CPython 2.3+ dan untuk hanya memilih pola "tambahkan / gabung" jika penjelas ini mengekspos ide untuk solusi masalah yang ada.
Dilettant

49

Operator Plus adalah solusi yang sangat baik untuk menggabungkan dua string Python. Tetapi jika Anda terus menambahkan lebih dari dua string (n> 25), Anda mungkin ingin memikirkan hal lain.

''.join([a, b, c]) trik adalah pengoptimalan kinerja.


2
Bukankah tupel lebih baik daripada daftar?
ThiefMaster

7
Tuple akan lebih cepat - kodenya hanyalah sebuah contoh :) Biasanya banyak input string yang panjang bersifat dinamis.
Mikko Ohtamaa

5
@martineau Saya pikir yang dia maksudkan secara dinamis membuat dan append()memasukkan string ke daftar.
Peter C

5
Perlu dikatakan di sini: tupel biasanya berstruktur lebih lambat, terutama jika sedang tumbuh. Dengan daftar Anda dapat menggunakan list.extend (list_of_items) dan list.append (item) yang jauh lebih cepat saat menggabungkan barang secara dinamis.
Antti Haapala

6
1 untuk n > 25. Manusia membutuhkan titik referensi untuk memulai suatu tempat.
n611x007

8

Asumsi bahwa seseorang seharusnya tidak pernah menggunakan + untuk penggabungan string, tetapi selalu menggunakan ".join mungkin hanya mitos. Memang benar bahwa menggunakan +membuat salinan sementara yang tidak perlu dari objek string yang tidak dapat diubah, tetapi fakta lain yang tidak sering dikutip adalah bahwa pemanggilan joindalam satu loop umumnya akan menambah overhead function call. Mari kita ambil contoh Anda.

Buat dua daftar, satu dari pertanyaan SO yang ditautkan dan yang lainnya dibuat lebih besar

>>> myl1 = ['A','B','C','D','E','F']
>>> myl2=[chr(random.randint(65,90)) for i in range(0,10000)]

Mari kita buat dua fungsi, UseJoindan UsePlusuntuk menggunakan masing-masing joindan +fungsionalitas.

>>> def UsePlus():
    return [myl[i] + myl[i + 1] for i in range(0,len(myl), 2)]

>>> def UseJoin():
    [''.join((myl[i],myl[i + 1])) for i in range(0,len(myl), 2)]

Mari kita jalankan timeit dengan daftar pertama

>>> myl=myl1
>>> t1=timeit.Timer("UsePlus()","from __main__ import UsePlus")
>>> t2=timeit.Timer("UseJoin()","from __main__ import UseJoin")
>>> print "%.2f usec/pass" % (1000000 * t1.timeit(number=100000)/100000)
2.48 usec/pass
>>> print "%.2f usec/pass" % (1000000 * t2.timeit(number=100000)/100000)
2.61 usec/pass
>>> 

Mereka memiliki runtime yang hampir sama.

Mari kita gunakan cProfile

>>> myl=myl2
>>> cProfile.run("UsePlus()")
         5 function calls in 0.001 CPU seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.001    0.001    0.001    0.001 <pyshell#1376>:1(UsePlus)
        1    0.000    0.000    0.001    0.001 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {len}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        1    0.000    0.000    0.000    0.000 {range}


>>> cProfile.run("UseJoin()")
         5005 function calls in 0.029 CPU seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.015    0.015    0.029    0.029 <pyshell#1388>:1(UseJoin)
        1    0.000    0.000    0.029    0.029 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {len}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
     5000    0.014    0.000    0.014    0.000 {method 'join' of 'str' objects}
        1    0.000    0.000    0.000    0.000 {range}

Dan tampaknya menggunakan Gabung, menghasilkan pemanggilan fungsi yang tidak perlu yang dapat menambah overhead.

Sekarang kembali ke pertanyaan. Haruskah seseorang mencegah penggunaan +overjoin dalam semua kasus?

Saya percaya tidak, hal-hal harus dipertimbangkan

  1. Panjang String dalam Pertanyaan
  2. Tidak Ada Operasi Penggabungan.

Dan di luar jalur dalam pengembangan, pengoptimalan pra-matang adalah jahat.


7
Tentu saja, idenya bukan untuk digunakan joindi dalam loop itu sendiri - melainkan loop akan menghasilkan urutan yang akan diteruskan untuk bergabung.
jsbueno

7

Saat bekerja dengan banyak orang, terkadang sulit untuk mengetahui dengan tepat apa yang terjadi. Menggunakan format string alih-alih penggabungan dapat menghindari satu gangguan tertentu yang terjadi berkali-kali kepada kami:

Katakanlah, sebuah fungsi membutuhkan argumen, dan Anda menulisnya dengan harapan mendapatkan string:

In [1]: def foo(zeta):
   ...:     print 'bar: ' + zeta

In [2]: foo('bang')
bar: bang

Jadi, fungsi ini mungkin cukup sering digunakan di seluruh kode. Rekan kerja Anda mungkin tahu persis apa fungsinya, tetapi belum tentu sepenuhnya mengetahui kecepatan internal, dan mungkin tidak tahu bahwa fungsi tersebut mengharapkan string. Dan mereka mungkin akan berakhir dengan ini:

In [3]: foo(23)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)

/home/izkata/<ipython console> in <module>()

/home/izkata/<ipython console> in foo(zeta)

TypeError: cannot concatenate 'str' and 'int' objects

Tidak akan ada masalah jika Anda hanya menggunakan string format:

In [1]: def foo(zeta):
   ...:     print 'bar: %s' % zeta
   ...:     
   ...:     

In [2]: foo('bang')
bar: bang

In [3]: foo(23)
bar: 23

Hal yang sama juga berlaku untuk semua jenis objek __str__yang ditentukan, yang mungkin diteruskan juga:

In [1]: from datetime import date

In [2]: zeta = date(2012, 4, 15)

In [3]: print 'bar: ' + zeta
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)

/home/izkata/<ipython console> in <module>()

TypeError: cannot concatenate 'str' and 'datetime.date' objects

In [4]: print 'bar: %s' % zeta
bar: 2012-04-15

Jadi ya: Jika Anda dapat menggunakan string format, lakukan dan manfaatkan apa yang ditawarkan Python.


1
+1 untuk perbedaan pendapat yang beralasan. Saya masih berpikir saya mendukung +.
Taymon

1
Mengapa Anda tidak mendefinisikan metode foo sebagai: print 'bar:' + str (zeta)?
EngineerWithJava54321

@ EngineerWithJava54321 Sebagai contoh, zeta = u"a\xac\u1234\u20ac\U00008000"- jadi Anda harus menggunakan print 'bar: ' + unicode(zeta)untuk memastikan itu tidak error. %smelakukannya dengan benar tanpa harus memikirkannya, dan jauh lebih pendek
Izkata

@ EngineerWithJava54321 Contoh lain kurang relevan di sini, tetapi misalnya, "bar: %s"mungkin diterjemahkan ke "zrb: %s br"dalam beberapa bahasa lain. The %sversi akan hanya bekerja, tapi versi string concat akan menjadi berantakan untuk menangani semua kasus dan penerjemah Anda sekarang akan memiliki dua terjemahan yang terpisah untuk menangani
Izkata

Jika mereka tidak tahu apa itu implementasi foo, mereka akan mengalami kesalahan ini dengan apapun def.
dalam

3

Saya telah melakukan tes cepat:

import sys

str = e = "a xxxxxxxxxx very xxxxxxxxxx long xxxxxxxxxx string xxxxxxxxxx\n"

for i in range(int(sys.argv[1])):
    str = str + e

dan waktunya:

mslade@mickpc:/binks/micks/ruby/tests$ time python /binks/micks/junk/strings.py  8000000
8000000 times

real    0m2.165s
user    0m1.620s
sys     0m0.540s
mslade@mickpc:/binks/micks/ruby/tests$ time python /binks/micks/junk/strings.py  16000000
16000000 times

real    0m4.360s
user    0m3.480s
sys     0m0.870s

Tampaknya ada optimasi untuk a = a + bkasus tersebut. Ini tidak menunjukkan waktu O (n ^ 2) seperti yang diduga.

Jadi setidaknya dalam hal kinerja, penggunaan +itu baik-baik saja.


3
Anda dapat membandingkan dengan kasus "bergabung" di sini. Dan ada masalah implementasi Python lainnya, seperti pypy, jython, ironpython, dll ...
jsbueno

3

Menurut dokumen Python, menggunakan str.join () akan memberi Anda konsistensi kinerja di berbagai implementasi Python. Meskipun CPython mengoptimalkan perilaku kuadrat s = s + t, implementasi Python lainnya mungkin tidak.

Detail implementasi CPython : Jika s dan t keduanya adalah string, beberapa implementasi Python seperti CPython biasanya dapat melakukan pengoptimalan di tempat untuk tugas dalam bentuk s = s + t atau s + = t. Jika berlaku, pengoptimalan ini membuat kemungkinan waktu berjalan kuadrat menjadi lebih kecil. Pengoptimalan ini bergantung pada versi dan implementasi. Untuk kode yang sensitif terhadap kinerja, lebih baik menggunakan metode str.join () yang menjamin kinerja penggabungan linier yang konsisten di seluruh versi dan implementasi.

Jenis Urutan dalam dokumen Python (lihat catatan kaki [6])


2

Saya menggunakan yang berikut ini dengan python 3.8

string4 = f'{string1}{string2}{string3}'

0

'' .join ([a, b]) adalah solusi yang lebih baik daripada + .

Karena Kode harus ditulis dengan cara yang tidak merugikan implementasi Python lainnya (PyPy, Jython, IronPython, Cython, Psyco, dan semacamnya)

bentuk a + = b atau a = a + b rapuh bahkan di CPython dan tidak ada sama sekali dalam implementasi yang tidak menggunakan penghitungan ulang (penghitungan referensi adalah teknik untuk menyimpan jumlah referensi, pointer, atau pegangan ke sumber daya seperti objek, blok memori, ruang disk, atau sumber daya lainnya )

https://www.python.org/dev/peps/pep-0008/#programming-recommendations


1
a += bbekerja di semua implementasi Python, hanya saja pada beberapa di antaranya dibutuhkan waktu kuadrat ketika dilakukan di dalam loop ; pertanyaannya adalah tentang penggabungan string luar loop.
Taymon
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.