Python'da en verimli dize birleştirme yöntemi nedir?


149

(Gibi Python herhangi verimli kitle dize birleştirme yöntemi var mıdır StringBuilder C # veya içinde StringBuffer Java)? Burada aşağıdaki yöntemleri buldum :

  • Kullanarak basit birleştirme +
  • Dize listesini ve joinyöntemini kullanma
  • Kullanımı UserStringgelen MutableStringmodül
  • Karakter dizisini ve arraymodülü kullanma
  • Kullanımı cStringIOgelen StringIOmodül

Ama uzmanlar ne kullanıyorsunuz veya öneriyorsunuz ve neden?

[ İlgili bir soru burada ]



Bilinen parçaları bir araya getirmek için Python 3.6, f''önceki Python sürümlerindeki alternatiflerden daha hızlı olacak format dizelerine sahip olacaktır.
Antti Haapala

Yanıtlar:


128

İlginizi çekebilir: Guido'nun bir optimizasyon fıkrası . Her ne kadar bunun eski bir makale olduğunu ve bunun gibi şeylerin varlığını önceden zikretmesine ''.joinrağmen (sanırım string.joinfieldsaşağı yukarı aynı olsa da)

O gücüne, arraymodül olabilir bunu içine sorununuzu ayakkabı çekeceği eğer hızlı olmak. Ancak ''.joinmuhtemelen yeterince hızlıdır ve deyimsel olma avantajına sahiptir ve bu nedenle diğer python programcılarının anlaması daha kolaydır.

Son olarak, optimizasyonun altın kuralı: bilmeniz gerekmediği sürece optimizasyon yapmayın ve tahmin etmek yerine ölçün.

timeitModülü kullanarak farklı yöntemleri ölçebilirsiniz . Bu , internette rasgele yabancılar yerine hangisinin en hızlı olduğunu söyleyebilir .


1
Ne zaman optimize edeceğinize dair bir nokta eklemek istemek: en kötü durumlara karşı test ettiğinizden emin olun. Örneğin, geçerli kodumun 0.17 saniyede 170 saniyeye çıkması için örneğimi artırabilirim. Daha az varyasyon olduğu için daha büyük örnek boyutlarında test etmek istiyorum.
Flipper

2
"İhtiyacınız olduğunu bilinceye kadar optimizasyon yapmayın." Sadece nominal olarak farklı bir deyim kullanmıyorsanız ve az çaba harcamadan kodunuzun yeniden çalışmasını önleyemezseniz.
jeremyjjbrown

1
İhtiyacınız olduğunu bildiğiniz bir yer röportajdır (derin anlayışınızı fırçalamak için her zaman harika bir zamandır). Ne yazık ki bu konuda modern bir makale bulamadım. (1) 2017'de Java / C # String hala o kadar kötü mü? (2) C ++ ne dersiniz? (3) Şimdi Python'da milyonlarca birleştirme yapmamız gereken durumlara odaklanan en son ve en büyük şeylerden bahsedin. Birleştirmenin doğrusal zamanda işe yarayacağına güvenebilir miyiz?
user1854182

"Yeterince hızlı" ne anlama geliyor .join()? Ana soru şudur: a) birleştirme için dizenin bir kopyasını oluşturmak (buna benzer s = s + 'abc'), bu da O (n) çalışma zamanını gerektirir veya b) O (1) gerektiren bir kopya oluşturmadan mevcut dizeye basitçe eklemek ?
CGFoX

64

''.join(sequenceofstrings) genellikle en iyi ve en hızlı şekilde çalışır.


3
@mshsayem, Python'da bir dizi numaralandırılabilir herhangi bir nesne, hatta bir işlev olabilir.
Nick Dandoulakis

2
''.join(sequence)Deyimi kesinlikle seviyorum . Virgülle ayrılmış listeler oluşturmak özellikle yararlıdır: ', '.join([1, 2, 3])dizeyi verir '1, 2, 3'.
Andrew Keeton

7
@mshsayem: "".join(chr(x) for x in xrange(65,91))--- bu durumda, birleştirme argümanı bir jeneratör ifadesi yoluyla oluşturulan bir yineleyicidir. İnşa edilecek geçici bir liste yok.
balpha

2
@balpha: ve yine de jeneratör sürümü liste kavrama sürümünden daha yavaş: C: \ temp> python -mtimeit "'' .join (xrange x (65,91)) için" chr (x) (65,91)) "100000 döngü, en iyisi 3: döngü başına 9,71 usec C: \ temp> python -mtimeit "'' .join (x in xrange (65,91)] için [chr (x)])" 100000 döngü, döngü başına 3: 7,1 usec
hughdbrown

1
@hughdbrown, evet, wazoo (tipik timeit durumda) listcomp boş bellek varsa, genellikle% 20-30 oranında genexp daha iyi optimize edilebilir. Hafızanın sıkı şeyleri farklı olduğunda - zaman içinde çoğaltılması zor olsa da! -)
Alex Martelli

58

Python 3.6, Literal String Interpolation ile bilinen bileşenlerin string birleştirme oyununu değiştirdi .

Mkoistinen'in cevabından test durumu göz önüne alındığında, iplere sahip olmak

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

Yarışmacılar

  • 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 (sabit uzunluklu bir demet oluşturmanın, sabit uzunluklu bir liste oluşturmaktan biraz daha hızlı olduğuna dikkat edin).

Böylece şu anda mümkün olan en kısa ve en güzel kod da en hızlısıdır.

Python 3.6'nın alfa versiyonlarında, f''dizelerin uygulanması mümkün olan en yavaştı - aslında üretilen bayt kodu hemen hemen''.join()str.__format__ argümanların selfdeğişmeden geri döneceği gereksiz çağrıların olduğu duruma . Bu verimsizlikler 3.6 finalden önce ele alınmıştır.

Hız, +bilgisayarımda birleştirme olan Python 2 için en hızlı yöntemle karşılaştırılabilir ; ve 8 bit dizelerle 0.203 µs ve dizelerin tümü Unicode ise 0.259 µs alır.


38

Ne yaptığınıza bağlı.

Python 2.5'ten sonra, + işleciyle dize birleştirmesi oldukça hızlıdır. Yalnızca birkaç değeri birleştiriyorsanız, + işlecini kullanmak en iyi sonucu verir:

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

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

Ancak, bir dizgiyi döngü içinde bir araya getiriyorsanız, liste birleştirme yöntemini kullanmanız daha iyi olur:

>>> 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

... ancak fark fark edilmeden önce nispeten yüksek sayıda dizeyi bir araya getirmeniz gerektiğini unutmayın.


2
1) İlk ölçümünüzde muhtemelen zaman alan liste yapısı vardır. Bir demet ile deneyin. 2) CPython eşit derecede iyi performans gösterir, ancak diğer Python uygulamaları + ve + =
u0b34a0f6ae ile

22

John Fouhy'nin cevabına göre, gerekmedikçe optimizasyon yapmayın, ancak buradaysanız ve bu soruyu soruyorsanız, bunun nedeni tam olarak zorunda olmanız olabilir . Benim durumumda, dize değişkenlerinden bazı URL'leri bir araya getirmem gerekiyordu ... hızlı. Hiç kimse (şimdiye kadar) dize biçimi yöntemi düşünüyor gibi görünüyor, bu yüzden ben bunu denemek düşündüm ve çoğunlukla hafif ilgi için, ben orada iyi ölçüm için orada dize enterpolasyon operatörü atmak düşündüm. Dürüst olmak gerekirse, bunların ikisinin de doğrudan bir '+' operasyonuna ya da bir '' .join () biriktireceğini düşünmemiştim. Ama tahmin et ne oldu? Python 2.7.5 sistemimde, dize enterpolasyon operatörü hepsini yönetir ve string.format () en kötü performanstır:

# 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), ))

Sonuçlar:

# 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)

Daha kısa bir etki alanı ve daha kısa bir yol kullanırsam enterpolasyon yine de kazanır. Fark daha uzun dizelerle daha belirgindir.

Şimdi güzel bir test senaryom vardı, Python 2.6, 3.3 ve 3.4 altında da test ettim, işte sonuçlar. Python 2.6'da, artı operatör en hızlısıdır! Python 3'te birleştirme kazanır. Not: Bu testler sistemimde çok tekrarlanabilir. Yani, 'artı' her zaman 2.6'da daha hızlı, 'intp' her zaman 2.7'de daha hızlı ve 'katıl' her zaman Python 3.x'te daha hızlıdır.

# 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)

Ders öğrenildi:

  • Bazen varsayımlarım yanlıştır.
  • Sisteme karşı test env. üretimde koşacaksınız.
  • Dize enterpolasyonu henüz ölmedi!

tl; dr:

  • 2.6 kullanıyorsanız + işlecini kullanın.
  • 2.7 kullanıyorsanız '%' operatörünü kullanın.
  • 3.x kullanıyorsanız '' .join () kullanın.

2
Not: gerçek dize enterpolasyonu 3.6+ için hala daha hızlıdır:f'http://{domain}/{lang}/{path}'
TemporalWolf

1
Ayrıca .format(): hızlı yavaş amacıyla üç formları vardır "{}".format(x), "{0}".format(x),"{x}".format(x=x)
TemporalWolf

Gerçek ders: sorun alanınız küçük olduğunda, örneğin kısa dizeler oluşturmak, yöntem çoğu zaman önemli değildir. Ve önemli olduğunda bile, örneğin gerçekten bir milyon tel inşa ediyorsunuz, genel gider genellikle daha önemli. Yanlış sorun hakkında endişelenmenin tipik bir belirtisidir. Yalnızca ek yük önemli olmadığında, örneğin kitabın tamamını dize olarak oluştururken, yöntem farkı önemli olmaya başlar.
Hui Zhou

7

hemen hemen her yeni birleştirme işleminden sonra yeni dizgenin göreli boyutlarına bağlıdır. İle +operatör, her birleştirme için yeni bir dize yapılır. Eğer ara dizeler nispeten uzunsa, +yeni ara dizeler saklandığından, gittikçe yavaşlar.

Bu durumu düşünün:

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

Sonuçlar

1 0.00493192672729

2 0.000509023666382

3 0.00042200088501

4 0.000482797622681

1 ve 2 durumunda, büyük bir dize ekliyoruz ve join () yaklaşık 10 kat daha hızlı performans gösteriyor. 3 ve 4 durumunda, küçük bir dize ekliyoruz ve '+' biraz daha hızlı çalışıyor


3

Bilinmeyen büyüklükte bir ipin olması gereken bir durumla karşılaştım. Bunlar karşılaştırma sonuçlarıdır (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

Bu en hızlı '+ =' olduğunu gösteriyor. Skymind bağlantısından sonuçlar biraz güncel değil.

(İkinci örneğin tamamlanmadığını, son listenin birleştirilmesi gerektiğini anlıyorum. Ancak bu, listeyi hazırlamanın dize bitişikliğinden daha uzun sürdüğünü gösteriyor.)


3. ve 4. testler için 1 saniyeden az süreler alıyorum. Neden bu kadar yüksek zamanlar alıyorsun? pastebin.com/qabNMCHS
bad_keypoints

@ronnieaka: Tüm testler için 1 saniyenin altında bir süre alıyor. O yapmadıysanız 3. ve 4. için > 1 µs alıyor . Ayrıca bu testlerde (Python 2.7.5, Linux) daha yavaş zamanlar alıyorum. CPU olabilir, sürüm, bayraklar inşa, kim bilir.
Thanatos

Bu karşılaştırma sonuçları işe yaramaz. Özellikle, herhangi bir dize birleştirmesi yapmayan, sadece ikinci dize değerini olduğu gibi döndüren ilk durum.
Antti Haapala

3

Bir yıl sonra, mkoistinen'in cevabını python 3.4.3 ile test edelim:

  • artı 0.963564149000 (% 95.83 hızlı)
  • 0.923408469000'e katılın (% 100.00 hızlı)
  • form 1.501130934000 (% 61,51 hızlı)
  • intp 1.019677452000 (% 90,56 hızlı)

Hiçbirşey değişmedi. Birleştirme hala en hızlı yöntemdir. İntp, okunabilirlik açısından tartışmasız en iyi seçenek olduğundan, yine de intp'yi kullanmak isteyebilirsiniz.


1
Belki de tam bir yanıttan biraz kısa olduğu için (veya en azından kullandığınız kodu ekleyin) mkoistinen cevabına bir ek olabilir.
Trilarion

1

@ JasonBaker kriterlerinden esinlenerek, 10 "abcdefghijklmnopqrstuvxyz"dizeyi karşılaştıran basit bir örnek ..join() , daha hızlı ; değişkenlerdeki bu küçük artışla bile:

Catenation

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

Katılmak

>>> x = timeit.Timer(stmt='"".join(["abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz"])')
>>> x.timeit()
0.6114138159765048

Bu sorunun kabul edilen cevabına bir göz atın (uzun aşağı kaydırın): stackoverflow.com/questions/1349311/…
mshsayem

1

Bir İçin küçük kümesi içinde kısa dizeleri (artık birkaç karakterden daha yani 2 veya 3 dizeleri), artı yolu daha hızlı hala. Python 2 ve 3'te mkoistinen'in harika senaryosunu kullanma:

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)

Kodunuzu Tercih şekilde ayrı küçük concatenations çok sayıda yapıyor, artı zaman Yani eğer hız çok önemlidir.


1

Muhtemelen "Python 3.6'daki yeni f-dizeleri", dizeleri birleştirmenin en etkili yoludur.

% S kullanma

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

.Format kullanma

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

F kullanma

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

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

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.