Rangkaian string vs. substitusi string dengan Python

98

Dengan Python, di mana dan kapan menggunakan penggabungan string versus substitusi string tidak dapat saya lakukan. Karena penggabungan string telah melihat peningkatan besar dalam kinerja, apakah ini (menjadi lebih) keputusan gaya daripada yang praktis?

Untuk contoh konkret, bagaimana seharusnya seseorang menangani konstruksi URI fleksibel:

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)

Sunting: Ada juga saran tentang bergabung dengan daftar string dan untuk menggunakan substitusi bernama. Ini adalah varian dari tema sentral, yaitu, pada saat mana Cara Benar melakukannya? Terima kasih atas tanggapannya!

Gotgenes
sumber
Lucu, di Ruby, interpolasi string umumnya lebih cepat daripada penggabungan ...
Keltia
Anda lupa mengembalikan "" .join ([DOMAIN, QUESTIONS, str (q_num)])
Jimmy
Saya bukan ahli Ruby, tapi saya berani bertaruh bahwa interpolasi lebih cepat karena string bisa berubah di Ruby. String adalah urutan tetap dalam Python.
gotgenes
1
hanya sedikit komentar tentang URI. URI tidak persis seperti string. Ada URI, jadi Anda harus sangat berhati-hati saat menggabungkan atau membandingkannya. Contoh: server yang mengirimkan representasinya melalui http pada port 80. example.org (no slah di akhir) example.org/ (slash) example.org:80/ (slah + port 80) adalah uri yang sama tetapi tidak sama tali.
karlcow

Jawaban:

55

Penggabungan (secara signifikan) lebih cepat menurut mesin saya. Tetapi secara gaya, saya bersedia membayar harga penggantian jika kinerja tidak kritis. Nah, dan jika saya perlu pemformatan, tidak perlu mengajukan pertanyaan ... tidak ada pilihan selain menggunakan interpolasi / templating.

>>> 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
Vinko Vrsalovic
sumber
10
apakah Anda melakukan pengujian dengan string yang sangat besar (seperti 100000 karakter)?
drnk
24

Jangan lupa tentang substitusi bernama:

def so_question_uri_namedsub(q_num):
    return "%(domain)s%(questions)s/%(q_num)d" % locals()
terlalu banyak php
sumber
4
Kode ini memiliki setidaknya 2 praktik pemrograman yang buruk: ekspektasi variabel global (domain dan pertanyaan tidak dideklarasikan di dalam fungsi) dan meneruskan lebih banyak variabel daripada yang dibutuhkan ke fungsi format (). Merendahkan karena jawaban ini mengajarkan praktik pengkodean yang buruk.
jperelli
12

Berhati-hatilah saat menggabungkan string dalam satu lingkaran! Biaya penggabungan string sebanding dengan panjang hasil. Pendauran membawa Anda langsung ke tanah N-kuadrat. Beberapa bahasa akan mengoptimalkan penggabungan ke string yang paling baru dialokasikan, tetapi berisiko mengandalkan kompiler untuk mengoptimalkan algoritme kuadrat Anda hingga linier. Paling baik menggunakan primitif ( join?) Yang mengambil seluruh daftar string, melakukan alokasi tunggal, dan menggabungkan semuanya sekaligus.

Norman Ramsey
sumber
16
Itu bukan saat ini. Dalam versi terbaru python, buffer string tersembunyi dibuat saat Anda menggabungkan string dalam satu lingkaran.
Seun Osewa
5
@ Seun: Ya, seperti yang saya katakan, beberapa bahasa akan dioptimalkan, tetapi ini adalah praktik yang berisiko.
Norman Ramsey
11

"Karena penggabungan string telah melihat peningkatan besar dalam performa ..."

Jika kinerja itu penting, ini bagus untuk diketahui.

Namun, masalah kinerja yang pernah saya lihat tidak pernah turun ke operasi string. Saya biasanya mendapat masalah dengan I / O, pengurutan dan operasi O ( n 2 ) menjadi hambatan.

Sampai operasi string menjadi pembatas kinerja, saya akan tetap menggunakan hal-hal yang sudah jelas. Sebagian besar, itu adalah substitusi ketika itu satu baris atau kurang, penggabungan ketika masuk akal, dan alat templat (seperti Mako) ketika itu besar.

S. Lott
sumber
10

Apa yang ingin Anda gabungkan / interpolasi dan bagaimana Anda ingin memformat hasilnya harus mendorong keputusan Anda.

  • Interpolasi string memungkinkan Anda menambahkan pemformatan dengan mudah. Nyatanya, versi interpolasi string Anda tidak melakukan hal yang sama seperti versi penggabungan Anda; itu sebenarnya menambahkan garis miring ke depan ekstra sebelum q_numparameter. Untuk melakukan hal yang sama, Anda harus menulis return DOMAIN + QUESTIONS + "/" + str(q_num)di contoh itu.

  • Interpolasi memudahkan untuk memformat angka; "%d of %d (%2.2f%%)" % (current, total, total/current)akan jauh lebih sulit dibaca dalam bentuk rangkaian.

  • Penggabungan berguna saat Anda tidak memiliki jumlah item tetap untuk dijadikan string.

Juga, ketahuilah bahwa Python 2.6 memperkenalkan versi baru dari interpolasi string, yang disebut template string :

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

String templating dijadwalkan untuk menggantikan% -interpolation, tapi itu tidak akan terjadi untuk waktu yang cukup lama, saya kira.

Tim Lesher
sumber
Ya, itu akan terjadi setiap kali Anda memutuskan untuk pindah ke python 3.0. Juga, lihat komentar Peter untuk fakta bahwa Anda dapat melakukan substitusi bernama dengan operator%.
John Fouhy
"Penggabungan berguna saat Anda tidak memiliki jumlah item tetap untuk dijadikan string." - Maksudmu daftar / larik? Dalam hal ini, tidak bisakah Anda bergabung saja () dengan mereka?
strager
"Tidak bisakah Anda bergabung dengan () mereka?" - Ya (dengan asumsi Anda ingin pemisah seragam antar item). Pemahaman daftar dan generator berfungsi baik dengan string.join.
Tim Lesher
1
"Yah, itu akan terjadi setiap kali Anda memutuskan untuk pindah ke python 3.0" - Tidak, py3k masih mendukung operator%. Titik depresiasi berikutnya yang mungkin adalah 3.1, jadi masih ada beberapa kehidupan di dalamnya.
Tim Lesher
2
2 tahun kemudian ... python 3.2 hampir dirilis dan% interpolasi gaya masih bagus.
Corey Goldberg
8

Saya baru saja menguji kecepatan metode penggabungan / substitusi string yang berbeda karena penasaran. Pencarian google tentang subjek membawa saya ke sini. Saya pikir saya akan memposting hasil tes saya dengan harapan dapat membantu seseorang memutuskan.

    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)

... Setelah berjalan runtests((percent_, format_, format2_, concat_), runs=5), saya menemukan bahwa% metode sekitar dua kali lebih cepat dari yang lain pada string kecil ini. Metode concat selalu yang paling lambat (hampir tidak). Ada perbedaan yang sangat kecil saat mengganti posisi dalam format()metode ini, tetapi posisi peralihan selalu setidaknya 0,01 lebih lambat daripada metode format biasa.

Contoh hasil tes:

    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)

Saya menjalankan ini karena saya menggunakan rangkaian string dalam skrip saya, dan saya bertanya-tanya berapa biayanya. Saya menjalankannya dalam urutan berbeda untuk memastikan tidak ada yang mengganggu, atau mendapatkan kinerja yang lebih baik menjadi yang pertama atau terakhir. Di samping catatan, saya memasukkan beberapa generator string yang lebih panjang ke dalam fungsi-fungsi seperti "%s" + ("a" * 1024)dan concat biasa hampir 3 kali lebih cepat (1.1 vs 2.8) daripada menggunakan metode formatdan %. Saya kira itu tergantung pada senar, dan apa yang ingin Anda capai. Jika kinerja benar-benar penting, mungkin lebih baik mencoba berbagai hal dan mengujinya. Saya cenderung memilih keterbacaan daripada kecepatan, kecuali kecepatan menjadi masalah, tapi itu hanya saya. JADI tidak suka salin / tempel saya, saya harus meletakkan 8 spasi pada semuanya agar terlihat benar. Saya biasanya menggunakan 4.

Cj Welborn
sumber
1
Anda harus secara serius mempertimbangkan bagaimana Anda membuat profil. Untuk satu concat Anda lambat karena Anda memiliki dua pemain str di dalamnya. Dengan string, hasilnya adalah sebaliknya, karena string concat sebenarnya lebih cepat daripada semua alternatif jika hanya tiga string yang diperhatikan.
Justus Wingert
@JustusWingert, ini sudah dua tahun sekarang. Saya telah belajar banyak sejak saya memposting 'tes' ini. Jujur saja, hari ini saya menggunakan str.format()dan str.join()lebih dari penggabungan normal. Saya juga mengawasi 'f-string' dari PEP 498 , yang baru-baru ini diterima. Adapun str()panggilan yang memengaruhi kinerja, saya yakin Anda benar tentang itu. Saya tidak tahu betapa mahalnya panggilan fungsi pada saat itu. Saya masih berpikir bahwa tes harus dilakukan jika ada keraguan.
Cj Welborn
Setelah pengujian cepat dengan join_(): return ''.join(["test ", str(1), ", with number ", str(2)]), tampaknya joinjuga lebih lambat dari persentase.
gaborous
4

Ingat, keputusan gaya adalah keputusan praktis, jika Anda berencana untuk mempertahankan atau men-debug kode Anda :-) Ada kutipan terkenal dari Knuth (mungkin mengutip Hoare?): "Kita harus melupakan tentang efisiensi kecil, katakan sekitar 97% dari waktu: pengoptimalan prematur adalah akar dari segala kejahatan. "

Selama Anda berhati-hati untuk tidak (mengatakan) mengubah tugas O (n) menjadi tugas O (n 2 ), saya akan memilih mana pun yang menurut Anda paling mudah dipahami ..

John Fouhy
sumber
0

Saya menggunakan substitusi di mana pun saya bisa. Saya hanya menggunakan penggabungan jika saya sedang membangun string di katakanlah for-loop.

Draemon
sumber
7
"membangun string dalam loop-for" - seringkali ini adalah kasus di mana Anda dapat menggunakan '' .join dan ekspresi generator ..
John Fouhy
-1

Sebenarnya hal yang benar untuk dilakukan, dalam hal ini (jalur pembangunan) adalah menggunakan os.path.join. Bukan penggabungan string atau interpolasi

hoskeri
sumber
1
itu benar untuk jalur os (seperti pada sistem file Anda) tetapi tidak saat membuat URI seperti dalam contoh ini. URI selalu memiliki '/' sebagai pemisah.
Andre Blum