İki dizeyi birleştirmek için '+' kullanmamak için herhangi bir neden var mı?


124

Python'daki yaygın bir antipattern +, bir döngü içinde kullanarak bir dizi dizeyi birleştirmektir . Bu kötüdür çünkü Python yorumlayıcısının her yineleme için yeni bir dizgi nesnesi oluşturması gerekir ve bu, ikinci dereceden zaman alır. (CPython'un son sürümleri görünüşe göre bunu bazı durumlarda optimize edebilir, ancak diğer uygulamalar bunu yapamaz, bu nedenle programcıların buna güvenmesi önerilmez.) ''.joinBunu yapmanın doğru yoludur.

Bununla birlikte, ( burada Stack Overflow dahil ) dize birleştirme için asla ve asla kullanmamanız gerektiğini +, bunun yerine her zaman ''.joinveya bir biçim dizesi kullanmanız gerektiğini söylediğini duydum . Sadece iki dizgeyi birleştiriyorsanız neden böyle olduğunu anlamıyorum. Benim anlayış doğru ise, o kuadratik zaman almamalıdır ve bence a + bdaha temiz ve ya daha okunabilir ''.join((a, b))veya '%s%s' % (a, b).

+İki dizeyi birleştirmek için kullanmak iyi bir uygulama mı ? Yoksa farkında olmadığım bir sorun mu var?


Daha temiz ve birleştirme yapmamak için daha fazla kontrole sahipsiniz. AMA onun biraz daha yavaş, dize dayak ticaret kapalı: P
Jakob Bowyer

+Daha hızlı mı yavaş mı diyorsun ? Ve neden?
Taymon

1
+ daha hızlı, 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 ve diğerleri: "Dize birleştirme kötü" argümanının hız ile neredeyse hiçbir ilgisi yoktur, ancak otomatik tür dönüştürmeden faydalanır __str__. Örnekler için cevabıma bakın.
Izkata

Yanıtlar:


120

İki dizgeyi birleştirmede yanlış bir şey yok +. Gerçekten okumaktan daha kolaydır ''.join([a, b]).

Haklısınız, ancak 2'den fazla dizeyi bitiştirmenin +bir O (n ^ 2) işlemi (O (n) için ile karşılaştırıldığında join) ve bu nedenle verimsiz hale geliyor. Ancak bunun bir döngü kullanmakla ilgisi yoktur. a + b + c + ...O (n ^ 2) bile çifttir, bunun nedeni her birleştirme işleminin yeni bir dize üretmesidir.

CPython2.4 ve üstü bunu azaltmaya çalışır, ancak yine de join2'den fazla dizeyi birleştirirken kullanılması tavsiye edilir .


5
@Mutant: .joinHer iki yüzden, bir iterable alır .join([a,b])ve .join((a,b))geçerlidir.
Foundling

1
İlginç zamanlamaları kullanarak ima +veya +=en (2013 itibaren) kabul cevap stackoverflow.com/a/12171382/378826 "/ append katılmak" bile seçti CPython için 2.3 ve sadece (Lennart Regebro itibaren) desen eğer bu daha net İFŞA eldeki problem çözümü için fikir.
Dilettant

49

Plus operatörü, iki Python dizesini birleştirmek için mükemmel bir çözümdür . Ancak ikiden fazla dizge eklemeye devam ederseniz (n> 25), başka bir şey düşünmek isteyebilirsiniz.

''.join([a, b, c]) hile bir performans optimizasyonudur.


2
Bir demet, bir listeden daha iyi olmaz mı?
ThiefMaster

7
Tuple daha hızlı olurdu - kod sadece bir örnekti :) Genellikle uzun çoklu dizgi girdileri dinamiktir.
Mikko Ohtamaa

5
@martineau Dinamik olarak append()dizeler üretmek ve bir listeye eklemek demek olduğunu düşünüyorum .
Peter C

5
Burada şunu söylemeliyim: tuple, özellikle büyüyorsa genellikle YAVAŞ yapıdadır. List ile, öğeleri dinamik olarak birleştirirken çok daha hızlı olan list.extend (list_of_items) ve list.append (öğe) seçeneklerini kullanabilirsiniz.
Antti Haapala

6
+1 n > 25. İnsanların bir yerden başlamak için referans noktalarına ihtiyacı vardır.
n611x007

8

Dize birleştirme için hiçbir zaman + kullanılmaması gerektiği, bunun yerine her zaman '' .join kullanılması gerektiği varsayımı bir efsane olabilir. Kullanmanın, +değişmez dizge nesnesinin gereksiz geçici kopyalarını oluşturduğu doğrudur, ancak çoğu kez alıntılanmayan diğer gerçek, joinbir döngü içinde çağırmanın genellikle ek yükünü ekleyeceğidir function call. Örneğinizi ele alalım.

Biri bağlantılı SO sorusundan ve diğeri daha büyük fabrikasyon olmak üzere iki liste oluşturun

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

İki işlevi oluşturmak, Lets UseJoinve UsePlusilgili kullanımı joinve +işlevselliği.

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

Timeit'i ilk listeyle çalıştıralım

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

Neredeyse aynı çalışma süresine sahipler.

CProfile kullanalım

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

Ve Görünüşe göre Join kullanmak, ek yüke neden olabilecek gereksiz işlev çağrılarına neden olur.

Şimdi soruya geri dönüyoruz. Her durumda +over kullanımını caydırmak gerekir joinmi?

Hayır inanıyorum, dikkate alınması gereken şeyler

  1. Söz konusu Dizenin Uzunluğu
  2. Birleştirme İşlemi Sayısı.

Ve tabii ki, gelişmeden önce olgunlaşmamış bir optimizasyon kötüdür.


7
Elbette fikir join, döngünün içinde kullanmak değil - bunun yerine döngü, birleşmek için geçilecek bir dizi oluşturacaktır.
jsbueno

7

Birden fazla kişiyle çalışırken, bazen tam olarak ne olduğunu bilmek zordur. Birleştirme yerine bir biçim dizesi kullanmak, başımıza pek çok kez gelen belirli bir rahatsızlığı önleyebilir:

Diyelim ki, bir işlev bir argüman gerektirir ve siz onu bir dizge almayı bekleyerek yazarsınız:

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

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

Bu nedenle, bu işlev kod boyunca oldukça sık kullanılabilir. İş arkadaşlarınız tam olarak ne yaptığını biliyor olabilir, ancak dahili bileşenlerde tam olarak hızlanmayabilir ve işlevin bir dizi beklediğini bilmeyebilir. Ve böylece bununla sonuçlanabilirler:

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

Sadece bir biçim dizesi kullandıysanız sorun olmaz:

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

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

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

Aynısı __str__, aynı zamanda aktarılabilen tanımlayan tüm nesne türleri için de geçerlidir :

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

Yani evet: Bir biçim dizesi kullanabiliyorsanız bunu ve Python teklif neler yararlanmak.


1
İyi gerekçeli bir muhalefet görüşü için +1. Yine de iyilik yaptığımı düşünüyorum +.
Taymon

1
Neden foo yöntemini şu şekilde tanımlamıyorsunuz: print 'bar:' + str (zeta)?
EngineerWithJava54321

@ EngineerWithJava54321 Bir örnek için zeta = u"a\xac\u1234\u20ac\U00008000"- bu yüzden print 'bar: ' + unicode(zeta)hata yapmamasını sağlamak için kullanmanız gerekir. %sbunu düşünmek zorunda kalmadan doğru mu ve çok daha kısa
Izkata

@ EngineerWithJava54321 Diğer örnekler burada daha az ilgilidir, ancak örneğin başka bir dile "bar: %s"çevrilebilir "zrb: %s br". %sVersiyon sadece çalışır, ancak dize concat sürümü tüm davalarına bakacak bir karmaşa olacak ve çevirmenleriniz şimdi başa iki ayrı çeviriler olurdu
Izkata

Foo uygulamasının ne olduğunu bilmiyorlarsa, bu hatayla herhangi biriyle karşılaşırlar def.
insidesin

3

Hızlı bir test yaptım:

import sys

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

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

ve zamanladı:

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

Görünüşe göre bir optimizasyon var a = a + b durum . Birinin tahmin edebileceği gibi O (n ^ 2) zamanı göstermez.

Yani en azından performans açısından kullanmak +iyidir.


3
Buradaki "katılma" durumuyla karşılaştırabilirsiniz. Ve pypy, jython, ironpython, vb. Gibi diğer Python uygulamaları meselesi var ...
jsbueno

3

Python belgelerine göre, str.join () kullanmak size çeşitli Python uygulamalarında performans tutarlılığı sağlayacaktır. CPython, s = s + t'nin ikinci dereceden davranışını optimize etse de, diğer Python uygulamaları olmayabilir.

CPython uygulama ayrıntısı : Eğer s ve t her iki dizgeyse, CPython gibi bazı Python uygulamaları genellikle s = s + t veya s + = t biçimindeki atamalar için yerinde bir optimizasyon gerçekleştirebilir. Mümkün olduğunda, bu optimizasyon ikinci dereceden çalışma zamanını çok daha az olası kılar. Bu optimizasyon hem sürüme hem de uygulamaya bağlıdır. Performansa duyarlı kod için, sürümler ve uygulamalar arasında tutarlı doğrusal birleştirme performansı sağlayan str.join () yönteminin kullanılması tercih edilir.

Python belgelerinde Dizi Türleri (dip nota [6] bakın)


2

Aşağıdakileri python 3.8 ile kullanıyorum

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

0

'' .join ([a, b]) + ' dan daha iyi bir çözümdür .

Çünkü Kod, Python'un diğer uygulamalarını (PyPy, Jython, IronPython, Cython, Psyco ve benzeri) dezavantajlı olmayacak şekilde yazılmalıdır.

formu, a + b = veya = a + b da CPython kırılgan ve uygulamalarda hiç mevcut olmayan bir kullanmayın refcounting referans sayılması için referanslar, işaretçileri veya kolları sayısını depolamak için bir tekniktir ( bir nesne, bellek bloğu, disk alanı veya diğer kaynaklar gibi kaynak )

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


1
a += bPython'un tüm uygulamalarında çalışır, sadece bazılarında döngü içinde yapıldığında ikinci dereceden zaman alır ; soru, bir döngünün dışındaki dize birleştirme hakkındaydı .
Taymon
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.