Python'da dize birleştirme ve dize değiştirme


98

Python'da, dize birleştirme yerine dize ikamesinin nerede ve ne zaman kullanılacağı benden kaçıyor. Dize birleştirme performansta büyük artışlar gördükçe, bu (daha fazla hale geliyor) pratik bir karar yerine stilistik bir karar mı?

Somut bir örnek için, esnek URI'lerin yapımı nasıl ele alınmalıdır:

DOMAIN = 'http://stackoverflow.com'
QUESTIONS = '/questions'

def so_question_uri_sub(q_num):
    return "%s%s/%d" % (DOMAIN, QUESTIONS, q_num)

def so_question_uri_cat(q_num):
    return DOMAIN + QUESTIONS + '/' + str(q_num)

Düzenleme: Dizeler listesine katılma ve adlandırılmış ikamenin kullanılmasıyla ilgili öneriler de vardır. Bunlar ana temanın varyantlarıdır, hangi zamanda bunu yapmanın Doğru Yolu hangisidir? Cevaplar için teşekkürler!


Komik, Ruby'de, dize enterpolasyonu genellikle birleştirmeden daha hızlıdır ...
Keltia

"" dönmeyi unuttunuz .join ([ALAN, SORULAR, str (q_num)])
Jimmy

Ruby uzmanı değilim, ancak aradeğerlemenin daha hızlı olduğuna bahse girerim çünkü dizeler Ruby'de değiştirilebilir. Dizeler Python'da değişmez dizilerdir.
gotgenes

1
URI'ler hakkında küçük bir yorum. URI'ler tam olarak dizeler gibi değildir. URI'ler vardır, bu yüzden onları birleştirirken veya karşılaştırırken çok dikkatli olmalısınız. Örnek: temsillerini http üzerinden 80 numaralı bağlantı noktasında sunan bir sunucu. Example.org (sonunda slah yok) example.org/ (eğik çizgi) example.org:80/ (slah + bağlantı noktası 80) aynı uri'dir ancak aynı değildir dize.
karlcow

Yanıtlar:


55

Birleştirme, makineme göre (önemli ölçüde) daha hızlı. Ama üslup olarak, performans kritik değilse, ikame bedelini ödemeye hazırım. Biçimlendirmeye ihtiyacım olursa, soruyu sormaya bile gerek yok ... enterpolasyon / şablonlama kullanmaktan başka seçenek yok.

>>> import timeit
>>> def so_q_sub(n):
...  return "%s%s/%d" % (DOMAIN, QUESTIONS, n)
...
>>> so_q_sub(1000)
'http://stackoverflow.com/questions/1000'
>>> def so_q_cat(n):
...  return DOMAIN + QUESTIONS + '/' + str(n)
...
>>> so_q_cat(1000)
'http://stackoverflow.com/questions/1000'
>>> t1 = timeit.Timer('so_q_sub(1000)','from __main__ import so_q_sub')
>>> t2 = timeit.Timer('so_q_cat(1000)','from __main__ import so_q_cat')
>>> t1.timeit(number=10000000)
12.166618871951641
>>> t2.timeit(number=10000000)
5.7813972166853773
>>> t1.timeit(number=1)
1.103492206766532e-05
>>> t2.timeit(number=1)
8.5206360154188587e-06

>>> def so_q_tmp(n):
...  return "{d}{q}/{n}".format(d=DOMAIN,q=QUESTIONS,n=n)
...
>>> so_q_tmp(1000)
'http://stackoverflow.com/questions/1000'
>>> t3= timeit.Timer('so_q_tmp(1000)','from __main__ import so_q_tmp')
>>> t3.timeit(number=10000000)
14.564135316080637

>>> def so_q_join(n):
...  return ''.join([DOMAIN,QUESTIONS,'/',str(n)])
...
>>> so_q_join(1000)
'http://stackoverflow.com/questions/1000'
>>> t4= timeit.Timer('so_q_join(1000)','from __main__ import so_q_join')
>>> t4.timeit(number=10000000)
9.4431309007150048

10
gerçek büyük dizelerle (100000 karakter gibi) testler yaptınız mı?
drnk

24

İsimlendirmeyi unutmayın:

def so_question_uri_namedsub(q_num):
    return "%(domain)s%(questions)s/%(q_num)d" % locals()

4
Bu kodun en az 2 kötü programlama uygulaması vardır: genel değişkenlerin beklentisi (etki alanı ve sorular işlev içinde bildirilmez) ve bir format () işlevine gerekenden daha fazla değişken geçirme. Olumsuz oylama, çünkü bu cevap kötü kodlama uygulamalarını öğretir.
jperelli

12

Bir döngüde dizeleri birleştirmeye karşı dikkatli olun! Dize birleştirme maliyeti, sonucun uzunluğu ile orantılıdır. Döngü sizi doğrudan N-kare diyarına götürür. Bazı diller, birleştirmeyi en son tahsis edilen diziye göre optimize eder, ancak ikinci dereceden algoritmanızı doğrusal olarak optimize etmek için derleyiciye güvenmek risklidir. Dize joinlistesinin tamamını alan, tek bir ayırma yapan ve hepsini tek seferde birleştiren ilkel ( ?) Kullanmak en iyisidir.


16
Bu güncel değil. Python'un en son sürümlerinde, bir döngüdeki dizeleri birleştirdiğinizde gizli bir dize tamponu oluşturulur.
Seun Osewa

5
@Seun: Evet, dediğim gibi, bazı diller optimize edilecek, ancak bu riskli bir uygulama.
Norman Ramsey

11

"Dize birleştirme performansta büyük artışlar gördükçe ..."

Performans önemliyse, bunu bilmek güzel.

Ancak, gördüğüm performans sorunları hiçbir zaman dizgi işlemlerine inmedi. Darboğazlar olan G / Ç, sıralama ve O ( n 2 ) işlemleriyle genellikle sorun yaşıyorum .

Dizgi işlemleri performans sınırlayıcıları olana kadar, bariz olan şeylere bağlı kalacağım. Çoğunlukla, bir satır veya daha az olduğunda ikame, mantıklı olduğunda birleştirme ve büyük olduğunda bir şablon aracı (Mako gibi).


10

Neyi birleştirmek / enterpolasyon yapmak istediğiniz ve sonucu nasıl biçimlendirmek istediğinize karar vermeniz gerekir.

  • Dize enterpolasyonu, kolayca biçimlendirme eklemenize olanak tanır. Aslında, dize enterpolasyon sürümünüz birleştirme sürümünüzle aynı şeyi yapmaz; aslında q_numparametrenin önüne fazladan bir eğik çizgi ekler . Aynı şeyi yapmak için return DOMAIN + QUESTIONS + "/" + str(q_num), o örnekte yazmanız gerekir .

  • Enterpolasyon, sayısalları biçimlendirmeyi kolaylaştırır; "%d of %d (%2.2f%%)" % (current, total, total/current)birleştirme biçiminde çok daha az okunabilir olacaktır.

  • Birleştirme, dizge oluşturacak sabit sayıda öğeniz olmadığında kullanışlıdır.

Ayrıca, Python 2.6'nın dize şablonu oluşturma adı verilen yeni bir dize enterpolasyonu sürümü sunduğunu bilin :

def so_question_uri_template(q_num):
    return "{domain}/{questions}/{num}".format(domain=DOMAIN,
                                               questions=QUESTIONS,
                                               num=q_num)

Dize şablonunun sonunda% -interpolation'ın yerini alması planlanıyor, ancak bu uzun bir süre olmayacak.


Pekala, python 3.0'a geçmeye karar verdiğinizde gerçekleşecek. Ayrıca, yine de% operatörü ile adlandırılmış ikameler yapabileceğiniz gerçeği için Peter'ın yorumuna bakın.
John Fouhy

"Birleştirme, dizge oluşturacak sabit sayıda öğeniz olmadığında kullanışlıdır." - Liste / dizi mi demek istiyorsun? Bu durumda, onlara () katılamaz mısın?
strager

"Onlara () katılamaz mısın?" - Evet (öğeler arasında tek tip ayırıcılar istediğiniz varsayılarak). String.join ile liste ve oluşturucu anlayışları harika çalışıyor.
Tim Lesher

1
"Pekala, python 3.0'a geçmeye karar verdiğinizde gerçekleşecek" - Hayır, py3k hala% operatörünü destekliyor. Bir sonraki olası kullanımdan kaldırma noktası 3.1, yani içinde hala biraz hayat var.
Tim Lesher

2
2 yıl sonra ... python 3.2 yayına yaklaşıyor ve% stil enterpolasyonu hala iyi durumda.
Corey Goldberg

8

Sadece meraktan farklı dizgi birleştirme / ikame yöntemlerinin hızını test ediyordum. Konuyla ilgili bir google araması beni buraya getirdi. Birisinin karar vermesine yardımcı olabileceği umuduyla test sonuçlarımı yayınlayacağımı düşündüm.

    import timeit
    def percent_():
            return "test %s, with number %s" % (1,2)

    def format_():
            return "test {}, with number {}".format(1,2)

    def format2_():
            return "test {1}, with number {0}".format(2,1)

    def concat_():
            return "test " + str(1) + ", with number " + str(2)

    def dotimers(func_list):
            # runs a single test for all functions in the list
            for func in func_list:
                    tmr = timeit.Timer(func)
                    res = tmr.timeit()
                    print "test " + func.func_name + ": " + str(res)

    def runtests(func_list, runs=5):
            # runs multiple tests for all functions in the list
            for i in range(runs):
                    print "----------- TEST #" + str(i + 1)
                    dotimers(func_list)

... Çalıştırdıktan sonra runtests((percent_, format_, format2_, concat_), runs=5), bu küçük dizelerde% yönteminin diğerlerine göre yaklaşık iki kat daha hızlı olduğunu buldum. Concat yöntemi her zaman en yavaş olanıydı (zar zor). Konumları değiştirirken çok küçük farklılıklar vardı.format()Yöntemdeki , ancak konum değiştirme her zaman normal biçim yönteminden en az 0,01 daha yavaştı.

Test sonuçları örneği:

    test concat_()  : 0.62  (0.61 to 0.63)
    test format_()  : 0.56  (consistently 0.56)
    test format2_() : 0.58  (0.57 to 0.59)
    test percent_() : 0.34  (0.33 to 0.35)

Bunları çalıştırdım çünkü komut dosyalarımda dize birleştirme kullanıyorum ve maliyetin ne olduğunu merak ediyordum. Hiçbir şeyin müdahale etmediğinden veya ilk veya son olarak daha iyi performans elde ettiğinden emin olmak için onları farklı sıralarla çalıştırdım. Bir yan not olarak, bazı daha uzun dizgi oluşturucuları gibi bu işlevlere "%s" + ("a" * 1024)ekledim ve normal concat, formatve %yöntemlerini kullanmaktan neredeyse 3 kat daha hızlıydı (1.1'e karşı 2.8) . Sanırım dizelere ve neyi başarmaya çalıştığınıza bağlı. Performans gerçekten önemliyse, farklı şeyler denemek ve onları test etmek daha iyi olabilir. Okunabilirliği hıza tercih etme eğilimindeyim, eğer hız bir sorun haline gelmedikçe, ama bu sadece benim. Yani kopyala / yapıştırımı beğenmedim, doğru görünmesi için her şeye 8 boşluk koymak zorunda kaldım. Genelde 4 kullanıyorum.


1
Neyin nasıl profilini çıkardığını ciddi olarak düşünmelisin. Birincisi, concat'iniz yavaştır çünkü içinde iki dizginiz vardır. Dizeler söz konusu olduğunda sonuç tam tersidir, çünkü dizge concat aslında sadece üç dizge söz konusu olduğunda tüm alternatiflerden daha hızlıdır.
Justus Wingert

@JustusWingert, bu artık iki yaşında. Bu 'testi' yayınladığımdan beri çok şey öğrendim. Açıkçası, bu gün kullandığım str.format()ve str.join()normal birleştirme üzerine. Ayrıca PEP 498'den 'f dizelerine' de göz kulak oluyorum , yakın zamanda kabul . str()Performansı etkileyen aramalara gelince , eminim bu konuda haklısınızdır. O zamanlar işlev çağrılarının ne kadar pahalı olduğu hakkında hiçbir fikrim yoktu. Hala şüphe olduğunda testlerin yapılması gerektiğini düşünüyorum.
Cj Welborn

Hızlı bir testten sonra join_(): return ''.join(["test ", str(1), ", with number ", str(2)]), joinyüzdeden daha yavaş görünüyor .
17:56

4

Üslup kararlar Unutmayın vardır Hiç korumak veya kod hata ayıklama planlıyorsanız, pratik kararlar :-) Knuth (? Olasılıkla Hoare'un alıntı) ünlü bir alıntı var: "Biz zamanın% 97 hakkında söylenecek, küçük verimlilik unutun olmalıdır: erken optimizasyon tüm kötülüklerin köküdür. "

Bir O (n) görevini O (n 2 ) görevine çevirmemeye (dememeye) dikkat ettiğiniz sürece , hangisini anlamak için en kolay bulursanız onunla giderim ..


0

Mümkün olan her yerde ikame kullanıyorum. Bir for-döngüsünde bir dizi oluşturuyorsam yalnızca bitiştirmeyi kullanırım.


7
"for-loop içinde bir dizi oluşturma" - genellikle bu, '' .join ve bir jeneratör ifadesi kullanabileceğiniz bir durumdur ..
John Fouhy

-1

Aslında yapılacak doğru şey, bu durumda (yol inşa etmek) kullanmaktır os.path.join. Dize birleştirme veya enterpolasyon değil


1
bu os yolları için doğrudur (dosya sisteminizdeki gibi), ancak bu örnekte olduğu gibi bir URI oluştururken değil. URI'lerin ayırıcı olarak her zaman "/" vardır.
Andre Blum
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.