Apa metode penggabungan string paling efisien dalam python?

148

Apakah ada metode gabungan string massa efisien dalam Python (seperti StringBuilder di C # atau StringBuffer di Jawa)? Saya menemukan metode berikut di sini :

  • Rangkaian sederhana menggunakan +
  • Menggunakan daftar string dan joinmetode
  • Menggunakan UserStringdari MutableStringmodul
  • Menggunakan array karakter dan arraymodul
  • Menggunakan cStringIOdari StringIOmodul

Tetapi apa yang Anda gunakan atau sarankan oleh para ahli, dan mengapa?

[ Pertanyaan terkait di sini ]

mshsayem
sumber
1
Pertanyaan serupa: stackoverflow.com/questions/476772
Peter Mortensen
Untuk menyatukan fragmen yang diketahui menjadi satu, Python 3.6 akan memiliki f''format string yang akan lebih cepat daripada alternatif apa pun di versi Python sebelumnya.
Antti Haapala

Jawaban:

127

Anda mungkin tertarik pada ini: Anekdot optimisasi oleh Guido. Meskipun perlu diingat juga bahwa ini adalah artikel lama dan mendahului keberadaan hal-hal seperti ''.join(walaupun saya kira string.joinfieldskurang lebih sama)

Pada kekuatan itu, arraymodul mungkin paling cepat jika Anda dapat memperbaiki masalah Anda ke dalamnya. Tetapi ''.joinmungkin cukup cepat dan memiliki manfaat menjadi idiomatis dan dengan demikian lebih mudah bagi programmer python lain untuk mengerti.

Akhirnya, aturan emas optimasi: jangan optimalkan kecuali Anda tahu Anda perlu, dan mengukur daripada menebak.

Anda dapat mengukur berbagai metode menggunakan timeitmodul. Itu bisa memberi tahu Anda mana yang tercepat, bukan orang asing acak di internet yang membuat tebakan.

John Fouhy
sumber
1
Ingin menambahkan poin tentang kapan harus mengoptimalkan: pastikan untuk menguji terhadap kasus terburuk. Misalnya, saya dapat menambah sampel saya sehingga kode saya saat ini berjalan dari 0,17 detik menjadi 170 detik. Yah saya ingin menguji pada ukuran sampel yang lebih besar karena ada sedikit variasi di sana.
Flipper
2
"Jangan mengoptimalkan sampai Anda tahu Anda perlu." Kecuali Anda hanya menggunakan idiom yang secara nominal berbeda dan dapat menghindari pengerjaan ulang kode Anda dengan sedikit usaha ekstra.
jeremyjjbrown
1
Satu tempat yang Anda tahu harus Anda wawancarai (yang selalu merupakan saat yang tepat untuk memoles pemahaman Anda yang mendalam). Sayangnya saya belum menemukan artikel modern tentang ini. (1) Apakah Java / C # String masih seburuk itu di tahun 2017? (2) Bagaimana dengan C ++? (3) Sekarang ceritakan tentang Python terbaru dan terhebat yang berfokus pada kasus-kasus ketika kita perlu melakukan jutaan penggabungan. Bisakah kita percaya bahwa bergabung akan bekerja dalam waktu linier?
user1854182
Apa artinya "cukup cepat" .join()? Pertanyaan utamanya adalah, apakah itu a) membuat salinan string untuk penggabungan (mirip dengan s = s + 'abc'), yang membutuhkan O (n) runtime, atau b) cukup menambahkan ke string yang ada tanpa membuat salinan, yang membutuhkan O (1) ?
CGFoX
64

''.join(sequenceofstrings) adalah apa yang biasanya bekerja paling baik - paling sederhana dan tercepat.

Alex Martelli
sumber
3
@ mshsayem, dalam Python suatu urutan dapat berupa objek enumerable, bahkan suatu fungsi.
Nick Dandoulakis
2
Saya benar-benar menyukai ''.join(sequence)ungkapan itu. Ini sangat berguna untuk menghasilkan daftar yang dipisahkan koma: ', '.join([1, 2, 3])memberikan string '1, 2, 3'.
Andrew Keeton
7
@ mshsayem: "".join(chr(x) for x in xrange(65,91))--- dalam kasus ini, argumen untuk bergabung adalah iterator, dibuat melalui ekspresi generator. Tidak ada daftar sementara yang dibangun.
balpha
2
@balpha: namun versi generator lebih lambat dari versi daftar pemahaman: C: \ temp> python -mtimeit "'' .join (chr (x) untuk x dalam xrange (65,91))" 100000 loop, terbaik dari 3: 9.71 usec per loop C: \ temp> python -mtimeit "'' .join ([chr (x) untuk x dalam xrange (65,91)])" 100000 loop, terbaik dari 3: 7,1 usec per loop
hughdbrown
1
@hughdbrown, ya, ketika Anda memiliki memori bebas dari listcomp wazoo (tipikal timeit) bisa lebih baik dioptimalkan daripada genexp, seringkali dengan 20-30%. Ketika hal-hal ketat ingatan berbeda - sulit untuk mereproduksi dalam waktu, meskipun! -)
Alex Martelli
58

Python 3.6 mengubah permainan untuk penggabungan string dari komponen yang diketahui dengan Literal String Interpolasi .

Diberikan kasus uji dari jawaban mkoistinen , memiliki string

domain = 'some_really_long_example.com'
lang = 'en'
path = 'some/really/long/path/'

Para pesaing adalah

  • f'http://{domain}/{lang}/{path}'- 0,151 μs

  • 'http://%s/%s/%s' % (domain, lang, path) - 0,321 μs

  • 'http://' + domain + '/' + lang + '/' + path - 0,356 μs

  • ''.join(('http://', domain, '/', lang, '/', path))- 0,249 µs (perhatikan bahwa membuat tupel panjang konstan sedikit lebih cepat daripada membuat daftar panjang konstan).

Dengan demikian saat ini kode terpendek dan terindah yang mungkin juga tercepat.

Dalam versi alpha dari Python 3.6 implementasi f''string adalah yang paling lambat mungkin - sebenarnya kode byte yang dihasilkan cukup setara dengan ''.join()kasus dengan panggilan yang tidak perlu str.__format__yang tanpa argumen hanya akan mengembalikan selftidak berubah. Inefisiensi ini ditangani sebelum 3,6 final.

Kecepatan dapat dikontraskan dengan metode tercepat untuk Python 2, yang merupakan +penggabungan pada komputer saya; dan itu membutuhkan 0,203 μs dengan string 8-bit, dan 0,259 μs jika semua string adalah Unicode.

Antti Haapala
sumber
38

Itu tergantung pada apa yang Anda lakukan.

Setelah Python 2.5, penggabungan string dengan operator + cukup cepat. Jika Anda hanya menggabungkan beberapa nilai, menggunakan + operator bekerja paling baik:

>>> x = timeit.Timer(stmt="'a' + 'b'")
>>> x.timeit()
0.039999961853027344

>>> x = timeit.Timer(stmt="''.join(['a', 'b'])")
>>> x.timeit()
0.76200008392333984

Namun, jika Anda menyusun string dalam satu lingkaran, Anda lebih baik menggunakan metode penggabungan daftar:

>>> join_stmt = """
... joined_str = ''
... for i in xrange(100000):
...   joined_str += str(i)
... """
>>> x = timeit.Timer(join_stmt)
>>> x.timeit(100)
13.278000116348267

>>> list_stmt = """
... str_list = []
... for i in xrange(100000):
...   str_list.append(str(i))
... ''.join(str_list)
... """
>>> x = timeit.Timer(list_stmt)
>>> x.timeit(100)
12.401000022888184

... tetapi perhatikan bahwa Anda harus mengumpulkan string yang relatif tinggi sebelum perbedaannya terlihat.

Jason Baker
sumber
2
1) Dalam pengukuran pertama Anda, mungkin daftar konstruksi yang membutuhkan waktu. Coba dengan tuple. 2) CPython berkinerja seragam baik, namun implementasi Python lainnya berkinerja lebih buruk dengan + dan + =
u0b34a0f6ae
22

Sesuai jawaban John Fouhy, jangan mengoptimalkan kecuali Anda harus, tetapi jika Anda di sini dan mengajukan pertanyaan ini, itu mungkin karena Anda harus melakukannya . Dalam kasus saya, saya perlu mengumpulkan beberapa URL dari variabel string ... cepat. Saya perhatikan tidak seorang pun (sejauh ini) tampaknya mempertimbangkan metode format string, jadi saya pikir saya akan mencobanya dan, sebagian besar untuk minat ringan, saya pikir saya akan melemparkan operator interpolasi string di sana untuk pengukuran yang baik. Sejujurnya, saya tidak berpikir salah satu dari ini akan menumpuk ke operasi '+' langsung atau '' .join (). Tapi coba tebak? Pada sistem Python 2.7.5 saya, operator interpolasi string mengatur semuanya dan string.format () adalah yang berkinerja terburuk:

# concatenate_test.py

from __future__ import print_function
import timeit

domain = 'some_really_long_example.com'
lang = 'en'
path = 'some/really/long/path/'
iterations = 1000000

def meth_plus():
    '''Using + operator'''
    return 'http://' + domain + '/' + lang + '/' + path

def meth_join():
    '''Using ''.join()'''
    return ''.join(['http://', domain, '/', lang, '/', path])

def meth_form():
    '''Using string.format'''
    return 'http://{0}/{1}/{2}'.format(domain, lang, path)

def meth_intp():
    '''Using string interpolation'''
    return 'http://%s/%s/%s' % (domain, lang, path)

plus = timeit.Timer(stmt="meth_plus()", setup="from __main__ import meth_plus")
join = timeit.Timer(stmt="meth_join()", setup="from __main__ import meth_join")
form = timeit.Timer(stmt="meth_form()", setup="from __main__ import meth_form")
intp = timeit.Timer(stmt="meth_intp()", setup="from __main__ import meth_intp")

plus.val = plus.timeit(iterations)
join.val = join.timeit(iterations)
form.val = form.timeit(iterations)
intp.val = intp.timeit(iterations)

min_val = min([plus.val, join.val, form.val, intp.val])

print('plus %0.12f (%0.2f%% as fast)' % (plus.val, (100 * min_val / plus.val), ))
print('join %0.12f (%0.2f%% as fast)' % (join.val, (100 * min_val / join.val), ))
print('form %0.12f (%0.2f%% as fast)' % (form.val, (100 * min_val / form.val), ))
print('intp %0.12f (%0.2f%% as fast)' % (intp.val, (100 * min_val / intp.val), ))

Hasil:

# python2.7 concatenate_test.py
plus 0.360787868500 (90.81% as fast)
join 0.452811956406 (72.36% as fast)
form 0.502608060837 (65.19% as fast)
intp 0.327636957169 (100.00% as fast)

Jika saya menggunakan domain yang lebih pendek dan jalur yang lebih pendek, interpolasi masih menang. Perbedaannya lebih jelas, dengan string yang lebih panjang.

Sekarang saya memiliki skrip pengujian yang bagus, saya juga menguji dengan Python 2.6, 3.3 dan 3.4, inilah hasilnya. Dalam Python 2.6, operator plus adalah yang tercepat! Pada Python 3, bergabunglah menang. Catatan: tes ini sangat berulang pada sistem saya. Jadi, 'plus' selalu lebih cepat di 2.6, 'intp' selalu lebih cepat di 2.7 dan 'join' selalu lebih cepat di Python 3.x.

# python2.6 concatenate_test.py
plus 0.338213920593 (100.00% as fast)
join 0.427221059799 (79.17% as fast)
form 0.515371084213 (65.63% as fast)
intp 0.378169059753 (89.43% as fast)

# python3.3 concatenate_test.py
plus 0.409130576998 (89.20% as fast)
join 0.364938726001 (100.00% as fast)
form 0.621366866995 (58.73% as fast)
intp 0.419064424001 (87.08% as fast)

# python3.4 concatenate_test.py
plus 0.481188605998 (85.14% as fast)
join 0.409673971997 (100.00% as fast)
form 0.652010936996 (62.83% as fast)
intp 0.460400978001 (88.98% as fast)

# python3.5 concatenate_test.py
plus 0.417167026084 (93.47% as fast)
join 0.389929617057 (100.00% as fast)
form 0.595661019906 (65.46% as fast)
intp 0.404455224983 (96.41% as fast)

Pelajaran yang dipelajari:

  • Terkadang, asumsi saya salah.
  • Uji terhadap sistem id. Anda akan berjalan dalam produksi.
  • Interpolasi string belum mati!

tl; dr:

  • Jika Anda menggunakan 2.6, gunakan operator +.
  • jika Anda menggunakan 2.7 gunakan operator '%'.
  • jika Anda menggunakan 3.x gunakan '' .join ().
mkoistinen
sumber
2
Catatan: interpolasi string literal masih lebih cepat untuk 3,6+:f'http://{domain}/{lang}/{path}'
TemporalWolf
1
Juga, .format()memiliki tiga bentuk, dalam urutan dari cepat untuk lambat: "{}".format(x), "{0}".format(x),"{x}".format(x=x)
TemporalWolf
Pelajaran nyata: ketika domain masalah Anda kecil, misalnya menyusun string pendek, metode yang paling sering tidak masalah. Dan bahkan ketika itu penting, misalnya Anda benar-benar membangun sejuta string, overhead sering kali lebih penting. Ini adalah gejala khas khawatir tentang masalah yang salah. Hanya ketika overhead tidak signifikan, misalnya ketika membangun seluruh buku sebagai string, perbedaan metode mulai menjadi masalah.
Hui Zhou
7

itu cukup banyak tergantung pada ukuran relatif dari string baru setelah setiap penggabungan baru. Dengan+ operator, untuk setiap rangkaian string baru dibuat. Jika string perantara relatif panjang, maka+ menjadi semakin lambat karena string perantara baru disimpan.

Pertimbangkan kasus ini:

from time import time
stri=''
a='aagsdfghfhdyjddtyjdhmfghmfgsdgsdfgsdfsdfsdfsdfsdfsdfddsksarigqeirnvgsdfsdgfsdfgfg'
l=[]
#case 1
t=time()
for i in range(1000):
    stri=stri+a+repr(i)
print time()-t

#case 2
t=time()
for i in xrange(1000):
    l.append(a+repr(i))
z=''.join(l)
print time()-t

#case 3
t=time()
for i in range(1000):
    stri=stri+repr(i)
print time()-t

#case 4
t=time()
for i in xrange(1000):
    l.append(repr(i))
z=''.join(l)
print time()-t

Hasil

1 0,00493192672729

2 0,000509023666382

3 0,00042200088501

4 0,000482797622681

Dalam kasus 1 & 2, kami menambahkan string besar, dan bergabung () melakukan sekitar 10 kali lebih cepat. Dalam kasus 3 & 4, kami menambahkan string kecil, dan '+' berkinerja sedikit lebih cepat

David Bielen
sumber
3

Saya mengalami situasi di mana saya perlu memiliki string yang tidak dapat ditambahkan ukurannya. Ini adalah hasil benchmark (python 2.7.3):

$ python -m timeit -s 's=""' 's+="a"'
10000000 loops, best of 3: 0.176 usec per loop
$ python -m timeit -s 's=[]' 's.append("a")'
10000000 loops, best of 3: 0.196 usec per loop
$ python -m timeit -s 's=""' 's="".join((s,"a"))'
100000 loops, best of 3: 16.9 usec per loop
$ python -m timeit -s 's=""' 's="%s%s"%(s,"a")'
100000 loops, best of 3: 19.4 usec per loop

Ini tampaknya menunjukkan bahwa '+ =' adalah yang tercepat. Hasil dari tautan skymind agak ketinggalan zaman.

(Saya menyadari bahwa contoh kedua tidak lengkap, daftar akhir perlu digabungkan. Namun, ini menunjukkan, bahwa hanya menyiapkan daftar memakan waktu lebih lama daripada string concat.)

MattK
sumber
Saya mendapatkan waktu 1 detik untuk pengujian 3 dan 4. Mengapa Anda mendapatkan waktu setinggi ini? pastebin.com/qabNMCHS
bad_keypoints
@ronnieaka: Dia mendapatkan waktu sub 1 detik untuk semua tes. Ia mendapatkan> 1 μs untuk tanggal 3 & 4, yang tidak Anda dapatkan . Saya juga mendapatkan waktu lebih lambat pada tes tersebut (pada Python 2.7.5, Linux). Bisa jadi CPU, versi, build flags, siapa tahu.
Thanatos
Hasil benchmark ini tidak berguna. Terutama, kasus pertama, yang tidak melakukan penggabungan string, hanya mengembalikan nilai string kedua utuh.
Antti Haapala
3

Satu Tahun kemudian, mari kita coba jawaban mkoistinen dengan python 3.4.3:

  • ditambah 0,963564149000 (95,83% lebih cepat)
  • bergabunglah 0,923408469000 (100,00% lebih cepat)
  • bentuk 1,501130934000 (61,51% lebih cepat)
  • intp 1.019677452000 (90,56% lebih cepat)

Tidak ada yang berubah. Bergabung masih merupakan metode tercepat. Dengan intp menjadi pilihan terbaik dalam hal keterbacaan, Anda mungkin ingin menggunakan intp.

Ramsch
sumber
1
Mungkin itu bisa menjadi tambahan untuk jawaban mkoistinen karena jawaban ini sedikit kurang lengkap (atau setidaknya menambahkan kode yang Anda gunakan).
Trilarion
1

Terinspirasi oleh tolok ukur @ JasonBaker, inilah yang sederhana membandingkan 10 "abcdefghijklmnopqrstuvxyz"string, menunjukkan itu.join() lebih cepat; bahkan dengan peningkatan kecil dalam variabel:

Catenation

>>> x = timeit.Timer(stmt='"abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz"')
>>> x.timeit()
0.9828147209324385

Ikuti

>>> x = timeit.Timer(stmt='"".join(["abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz"])')
>>> x.timeit()
0.6114138159765048
DI
sumber
Lihatlah jawaban yang diterima (gulir ke bawah panjang) dari pertanyaan ini: stackoverflow.com/questions/1349311/…
mshsayem
1

Untuk set kecil dari string pendek (yaitu 2 atau 3 string tidak lebih dari beberapa karakter), ditambah masih cara yang lebih cepat. Menggunakan skrip mkoistinen yang luar biasa dalam Python 2 dan 3:

plus 2.679107467004 (100.00% as fast)
join 3.653773699996 (73.32% as fast)
form 6.594011374000 (40.63% as fast)
intp 4.568015249999 (58.65% as fast)

Jadi ketika kode Anda melakukan sejumlah besar rangkaian kecil terpisah, plus adalah cara yang disukai jika kecepatan sangat penting.

pengguna7505681
sumber
1

Mungkin "f-string baru dengan Python 3.6" adalah cara yang paling efisien untuk merangkai string.

Menggunakan% s

>>> timeit.timeit("""name = "Some"
... age = 100
... '%s is %s.' % (name, age)""", number = 10000)
0.0029734770068898797

Menggunakan .format

>>> timeit.timeit("""name = "Some"
... age = 100
... '{} is {}.'.format(name, age)""", number = 10000)
0.004015227983472869

Menggunakan f

>>> timeit.timeit("""name = "Some"
... age = 100
... f'{name} is {age}.'""", number = 10000)
0.0019175919878762215

Sumber: https://realpython.com/python-f-strings/

SuperNova
sumber