Mana cara yang lebih disukai untuk menyatukan string dengan Python?

358

Karena Python stringtidak dapat diubah, saya bertanya-tanya bagaimana cara menggabungkan string yang lebih efisien?

Saya bisa menulis seperti itu:

s += stringfromelsewhere

atau seperti ini:

s = []
s.append(somestring)

later

s = ''.join(s)

Saat menulis pertanyaan ini, saya menemukan artikel yang bagus berbicara tentang topik tersebut.

http://www.skymind.com/~ocrow/python_string/

Tapi itu di Python 2.x., jadi pertanyaannya adalah apakah ada perubahan di Python 3?

Maks
sumber

Jawaban:

433

Cara terbaik menambahkan string ke variabel string adalah dengan menggunakan +atau +=. Ini karena mudah dibaca dan cepat. Mereka juga sama cepatnya, yang mana yang Anda pilih adalah masalah selera, yang terakhir adalah yang paling umum. Berikut adalah pengaturan waktu dengan timeitmodul:

a = a + b:
0.11338996887207031
a += b:
0.11040496826171875

Namun, mereka yang merekomendasikan memiliki daftar dan menambahkannya kemudian bergabung dengan daftar itu, melakukannya karena menambahkan string ke daftar mungkin sangat cepat dibandingkan dengan memperpanjang string. Dan ini bisa benar, dalam beberapa kasus. Di sini, misalnya, adalah satu juta penambahan string satu karakter, pertama ke string, lalu ke daftar:

a += b:
0.10780501365661621
a.append(b):
0.1123361587524414

OK, ternyata bahkan ketika string yang dihasilkan adalah sejuta karakter, menambahkan masih lebih cepat.

Sekarang mari kita coba dengan menambahkan string panjang karakter seribu seratus ribu kali:

a += b:
0.41823482513427734
a.append(b):
0.010656118392944336

Oleh karena itu, string akhir memiliki panjang sekitar 100MB. Itu sangat lambat, menambahkan daftar jauh lebih cepat. Bahwa waktu itu tidak termasuk final a.join(). Jadi berapa lama?

a.join(a):
0.43739795684814453

Oups Ternyata bahkan dalam kasus ini, tambahkan / gabung lebih lambat.

Jadi dari mana datangnya rekomendasi ini? Python 2?

a += b:
0.165287017822
a.append(b):
0.0132720470428
a.join(a):
0.114929914474

Nah, tambahkan / gabungkan sedikit lebih cepat di sana jika Anda menggunakan string yang sangat panjang (yang biasanya tidak Anda miliki, apa yang akan Anda miliki string yang 100 MB dalam memori?)

Tetapi penentu nyata adalah Python 2.3. Di mana saya bahkan tidak akan menunjukkan waktu kepada Anda, karena itu sangat lambat sehingga belum selesai. Tes-tes ini tiba-tiba memakan waktu beberapa menit . Kecuali untuk append / join, yang sama cepatnya dengan Python berikutnya.

Ya. Rangkaian string sangat lambat di Python kembali di zaman batu. Tetapi pada 2.4 itu tidak lagi (atau setidaknya Python 2.4.7), jadi rekomendasi untuk menggunakan append / join menjadi usang pada 2008, ketika Python 2.3 berhenti diperbarui, dan Anda seharusnya berhenti menggunakannya. :-)

(Pembaruan: Ternyata ketika saya melakukan pengujian lebih hati-hati yang menggunakan +dan +=lebih cepat untuk dua string pada Python 2.3 juga. Rekomendasi untuk menggunakan ''.join()harus kesalahpahaman)

Namun, ini adalah CPython. Implementasi lain mungkin memiliki masalah lain. Dan ini hanyalah alasan lain mengapa optimasi prematur adalah akar dari semua kejahatan. Jangan gunakan teknik yang seharusnya "lebih cepat" kecuali Anda mengukurnya terlebih dahulu.

Oleh karena itu versi "terbaik" untuk melakukan penggabungan string adalah dengan menggunakan + atau + = . Dan jika itu ternyata lambat bagi Anda, yang sangat tidak mungkin, maka lakukan sesuatu yang lain.

Jadi mengapa saya menggunakan banyak append / join di kode saya? Karena terkadang itu sebenarnya lebih jelas. Terutama ketika apa pun yang Anda harus menyatukan bersama harus dipisahkan oleh spasi atau koma atau baris baru.

Lennart Regebro
sumber
10
Jika Anda memiliki banyak string (n> 10) "" .join (list_of_strings) masih lebih cepat
Mikko Ohtamaa
11
alasan mengapa + = cepat adalah, bahwa ada peretasan kinerja di cpython jika refcount adalah 1 - itu berantakan pada hampir semua implementasi python lainnya (dengan pengecualian dari pypy build yang dikonfigurasi khusus)
Ronny
17
Mengapa hal ini begitu dibesarkan hati? Bagaimana cara yang lebih baik untuk menggunakan algoritma yang hanya efisien pada satu implementasi spesifik dan memiliki apa yang pada dasarnya merupakan hack yang rapuh untuk memperbaiki algoritma waktu kuadratik? Anda juga benar-benar salah paham titik "optimasi prematur adalah akar dari semua kejahatan". Kutipan itu berbicara tentang optimisasi KECIL. Ini bergerak dari O (n ^ 2) ke O (n) yang BUKAN optimasi kecil.
Wes
12
Berikut adalah kutipan yang sebenarnya: "Kita harus melupakan efisiensi kecil, katakanlah sekitar 97% dari waktu: optimasi prematur adalah akar dari semua kejahatan. Namun kita tidak boleh melewatkan peluang kita dalam 3% kritis. Seorang programmer yang baik tidak akan ditidurkan dengan alasan seperti itu, ia akan bijaksana untuk melihat dengan hati-hati kode kritis; tetapi hanya setelah kode itu diidentifikasi "
Wes
2
Tidak ada yang mengatakan bahwa a + b lambat. Ini kuadrat ketika Anda melakukan a = a + b lebih dari sekali. a + b + c tidak lambat, saya ulangi tidak lambat karena hanya harus melintasi setiap string sekali, sedangkan itu harus melintasi ulang string sebelumnya berkali-kali dengan pendekatan a = a + b (dengan asumsi bahwa itu dalam satu lingkaran sejenis). Ingat string tidak berubah.
Wes
52

Jika Anda menggabungkan banyak nilai, maka keduanya juga tidak. Menambahkan daftar itu mahal. Anda dapat menggunakan StringIO untuk itu. Terutama jika Anda membangunnya di banyak operasi.

from cStringIO import StringIO
# python3:  from io import StringIO

buf = StringIO()

buf.write('foo')
buf.write('foo')
buf.write('foo')

buf.getvalue()
# 'foofoofoo'

Jika Anda sudah memiliki daftar lengkap yang dikembalikan kepada Anda dari beberapa operasi lain, maka cukup gunakan ''.join(aList)

Dari python FAQ: Apa cara paling efisien untuk menggabungkan banyak string bersama?

objek str dan byte tidak dapat diubah, oleh karena itu menggabungkan banyak string secara bersamaan tidak efisien karena setiap concatenation menciptakan objek baru. Dalam kasus umum, total biaya runtime kuadrat dalam total panjang string.

Untuk mengakumulasi banyak objek str, idiom yang disarankan adalah menempatkannya ke dalam daftar dan memanggil str.join () di akhir:

chunks = []
for s in my_strings:
    chunks.append(s)
result = ''.join(chunks)

(Idiom lain yang cukup efisien adalah menggunakan io.StringIO)

Untuk mengakumulasikan banyak objek byte, idiom yang disarankan adalah memperluas objek bytearray menggunakan in-place concatenation (operator + =):

result = bytearray()
for b in my_bytes_objects:
    result += b

Sunting: Saya bodoh dan hasilnya disisipkan mundur, membuatnya tampak seperti menambahkan daftar lebih cepat daripada cStringIO. Saya juga telah menambahkan tes untuk concat bytearray / str, serta tes putaran kedua menggunakan daftar yang lebih besar dengan string yang lebih besar. (python 2.7.3)

contoh uji ipython untuk daftar string yang besar

try:
    from cStringIO import StringIO
except:
    from io import StringIO

source = ['foo']*1000

%%timeit buf = StringIO()
for i in source:
    buf.write(i)
final = buf.getvalue()
# 1000 loops, best of 3: 1.27 ms per loop

%%timeit out = []
for i in source:
    out.append(i)
final = ''.join(out)
# 1000 loops, best of 3: 9.89 ms per loop

%%timeit out = bytearray()
for i in source:
    out += i
# 10000 loops, best of 3: 98.5 µs per loop

%%timeit out = ""
for i in source:
    out += i
# 10000 loops, best of 3: 161 µs per loop

## Repeat the tests with a larger list, containing
## strings that are bigger than the small string caching 
## done by the Python
source = ['foo']*1000

# cStringIO
# 10 loops, best of 3: 19.2 ms per loop

# list append and join
# 100 loops, best of 3: 144 ms per loop

# bytearray() +=
# 100 loops, best of 3: 3.8 ms per loop

# str() +=
# 100 loops, best of 3: 5.11 ms per loop
jdi
sumber
2
cStringIOtidak ada di Py3. Gunakan io.StringIOsebagai gantinya.
lvc
2
Adapun mengapa menambahkan string berulang kali bisa mahal: joelonsoftware.com/articles/fog0000000319.html
Wes
36

Dalam Python> = 3.6, f-string baru adalah cara yang efisien untuk menggabungkan string.

>>> name = 'some_name'
>>> number = 123
>>>
>>> f'Name is {name} and the number is {number}.'
'Name is some_name and the number is 123.'
SuperNova
sumber
8

Metode yang disarankan masih menggunakan append dan gabung.

MRAB
sumber
1
Seperti yang Anda lihat dari jawaban saya, ini tergantung pada berapa banyak string yang Anda gabungkan. Saya telah melakukan beberapa pengaturan waktu untuk hal ini (lihat ceramah yang saya tautkan dalam komentar saya pada jawaban saya) dan umumnya kecuali lebih dari sepuluh, gunakan +.
Lennart Regebro
1
PEP8 menyebutkan ini ( python.org/dev/peps/pep-0008/#programming-recomendations ). Rasionalnya adalah bahwa sementara CPython memiliki optimasi khusus untuk rangkaian string dengan + =, implementasi lain mungkin tidak.
Quantum7
8

Jika string yang Anda gabungkan adalah literal, gunakan string literal string

re.compile(
        "[A-Za-z_]"       # letter or underscore
        "[A-Za-z0-9_]*"   # letter, digit or underscore
    )

Ini berguna jika Anda ingin mengomentari sebagian string (seperti di atas) atau jika Anda ingin menggunakan string mentah atau tiga kutipan untuk bagian literal tetapi tidak semua.

Karena ini terjadi pada lapisan sintaks menggunakan operator concatenation nol.

droid
sumber
7

Anda menulis fungsi ini

def str_join(*args):
    return ''.join(map(str, args))

Maka Anda dapat menelepon ke mana saja Anda inginkan

str_join('Pine')  # Returns : Pine
str_join('Pine', 'apple')  # Returns : Pineapple
str_join('Pine', 'apple', 3)  # Returns : Pineapple3
Shameem
sumber
1
str_join = lambda *str_list: ''.join(s for s in str_list)
Rick mendukung Monica
7

Menggunakan penggabungan string di tempat dengan '+' adalah metode penggabungan THE WORST dalam hal stabilitas dan implementasi lintas karena tidak mendukung semua nilai. Standar PEP8 tidak mendukung hal ini dan mendorong penggunaan format (), join () dan append () untuk penggunaan jangka panjang.

Seperti dikutip dari bagian "Rekomendasi Pemrograman" yang ditautkan:

Sebagai contoh, jangan bergantung pada implementasi efisien dari rangkaian string in-place CPython untuk pernyataan dalam bentuk a = = b atau a = a + b. Optimasi ini rapuh bahkan dalam CPython (hanya berfungsi untuk beberapa jenis) dan tidak ada sama sekali dalam implementasi yang tidak menggunakan penghitungan ulang. Di bagian peka kinerja yang sensitif, formulir '.join () harus digunakan sebagai gantinya. Ini akan memastikan bahwa rangkaian terjadi dalam waktu linier di berbagai implementasi.

badslacks
sumber
5
Tautan referensi pasti menyenangkan :)
6

Sementara agak tanggal, Kode Seperti Pythonista: idiomatic Python merekomendasikan join()lebih + di bagian ini . Seperti halnya PythonSpeedPerformanceTips di bagiannya pada string concatenation , dengan penafian berikut:

Keakuratan bagian ini diperselisihkan sehubungan dengan versi Python yang lebih baru. Dalam CPython 2.5, penggabungan string cukup cepat, meskipun ini mungkin tidak berlaku juga untuk implementasi Python lainnya. Lihat ConcatenationTestCode untuk diskusi.

Levon
sumber
6

Seperti @jdi menyebutkan dokumentasi Python menyarankan untuk menggunakan str.joinatau io.StringIOuntuk penggabungan string. Dan mengatakan bahwa pengembang harus mengharapkan waktu kuadrat dari +=dalam satu lingkaran, meskipun ada optimasi sejak Python 2.4. Seperti jawaban ini mengatakan:

Jika Python mendeteksi bahwa argumen kiri tidak memiliki referensi lain, itu panggilan reallocuntuk mencoba menghindari salinan dengan mengubah ukuran string pada tempatnya. Ini bukan sesuatu yang harus Anda andalkan, karena ini adalah detail implementasi dan karena jika reallocakhirnya perlu sering-sering memindahkan string, kinerja akan menurun ke O (n ^ 2).

Saya akan menunjukkan contoh kode dunia nyata yang secara naif mengandalkan +=optimasi ini, tetapi tidak berlaku. Kode di bawah ini mengubah iterable string pendek menjadi potongan yang lebih besar untuk digunakan dalam API massal.

def test_concat_chunk(seq, split_by):
    result = ['']
    for item in seq:
        if len(result[-1]) + len(item) > split_by: 
            result.append('')
        result[-1] += item
    return result

Kode ini dapat dijalankan selama berjam-jam karena kompleksitas waktu kuadratik. Di bawah ini adalah alternatif dengan struktur data yang disarankan:

import io

def test_stringio_chunk(seq, split_by):
    def chunk():
        buf = io.StringIO()
        size = 0
        for item in seq:
            if size + len(item) <= split_by:
                size += buf.write(item)
            else:
                yield buf.getvalue()
                buf = io.StringIO()
                size = buf.write(item)
        if size:
            yield buf.getvalue()

    return list(chunk())

def test_join_chunk(seq, split_by):
    def chunk():
        buf = []
        size = 0
        for item in seq:
            if size + len(item) <= split_by:
                buf.append(item)
                size += len(item)
            else:
                yield ''.join(buf)                
                buf.clear()
                buf.append(item)
                size = len(item)
        if size:
            yield ''.join(buf)

    return list(chunk())

Dan tolok ukur mikro:

import timeit
import random
import string
import matplotlib.pyplot as plt

line = ''.join(random.choices(
    string.ascii_uppercase + string.digits, k=512)) + '\n'
x = []
y_concat = []
y_stringio = []
y_join = []
n = 5
for i in range(1, 11):
    x.append(i)
    seq = [line] * (20 * 2 ** 20 // len(line))
    chunk_size = i * 2 ** 20
    y_concat.append(
        timeit.timeit(lambda: test_concat_chunk(seq, chunk_size), number=n) / n)
    y_stringio.append(
        timeit.timeit(lambda: test_stringio_chunk(seq, chunk_size), number=n) / n)
    y_join.append(
        timeit.timeit(lambda: test_join_chunk(seq, chunk_size), number=n) / n)
plt.plot(x, y_concat)
plt.plot(x, y_stringio)
plt.plot(x, y_join)
plt.legend(['concat', 'stringio', 'join'], loc='upper left')
plt.show()

tolok ukur mikro

Saaj
sumber
5

Anda dapat melakukannya dengan berbagai cara.

str1 = "Hello"
str2 = "World"
str_list = ['Hello', 'World']
str_dict = {'str1': 'Hello', 'str2': 'World'}

# Concatenating With the + Operator
print(str1 + ' ' + str2)  # Hello World

# String Formatting with the % Operator
print("%s %s" % (str1, str2))  # Hello World

# String Formatting with the { } Operators with str.format()
print("{}{}".format(str1, str2))  # Hello World
print("{0}{1}".format(str1, str2))  # Hello World
print("{str1} {str2}".format(str1=str_dict['str1'], str2=str_dict['str2']))  # Hello World
print("{str1} {str2}".format(**str_dict))  # Hello World

# Going From a List to a String in Python With .join()
print(' '.join(str_list))  # Hello World

# Python f'strings --> 3.6 onwards
print(f"{str1} {str2}")  # Hello World

Saya membuat ringkasan kecil ini melalui artikel-artikel berikut.

Kushan Gunasekera
sumber
3

kasing saya sedikit berbeda. Saya harus membuat kueri tempat lebih dari 20 bidang dinamis. Saya mengikuti pendekatan ini menggunakan metode format

query = "insert into {0}({1},{2},{3}) values({4}, {5}, {6})"
query.format('users','name','age','dna','suzan',1010,'nda')

ini relatif lebih mudah bagi saya daripada menggunakan + atau cara lain

Ishwar Rimal
sumber