Phương pháp nối chuỗi hiệu quả nhất trong python là gì?


148

Có phương pháp nối chuỗi khối hiệu quả nào trong Python (như StringBuilder trong C # hoặc StringBuffer trong Java) không? Tôi tìm thấy các phương pháp sau đây :

  • Ghép đơn giản bằng cách sử dụng +
  • Sử dụng danh sách chuỗi và joinphương thức
  • Sử dụng UserStringtừ MutableStringmô-đun
  • Sử dụng mảng ký tự và arraymô-đun
  • Sử dụng cStringIOtừ StringIOmô-đun

Nhưng những gì các chuyên gia sử dụng hoặc đề nghị, và tại sao?

[ Một câu hỏi liên quan ở đây ]



Để ghép các đoạn đã biết thành một, Python 3.6 sẽ có các f''chuỗi định dạng sẽ nhanh hơn bất kỳ lựa chọn thay thế nào trong các phiên bản Python trước.
Antti Haapala

Câu trả lời:


127

Bạn có thể quan tâm đến điều này: Một giai thoại tối ưu hóa của Guido. Mặc dù cũng đáng để nhớ rằng đây là một bài viết cũ và nó có trước sự tồn tại của những thứ như ''.join(mặc dù tôi đoán string.joinfieldslà ít nhiều giống nhau)

Trên sức mạnh của điều đó, arraymô-đun có thể là nhanh nhất nếu bạn có thể khắc phục vấn đề của bạn vào nó. Nhưng ''.joincó lẽ là đủ nhanh và có lợi ích là thành ngữ và do đó dễ dàng hơn cho các lập trình viên trăn khác để hiểu.

Cuối cùng, nguyên tắc tối ưu hóa vàng: không tối ưu hóa trừ khi bạn biết bạn cần và đo lường thay vì đoán.

Bạn có thể đo các phương pháp khác nhau bằng cách sử dụng timeitmô-đun. Điều đó có thể cho bạn biết cái nào nhanh nhất, thay vì những người lạ ngẫu nhiên trên internet đưa ra dự đoán.


1
Muốn thêm vào điểm về thời điểm tối ưu hóa: hãy đảm bảo kiểm tra các trường hợp xấu nhất. Ví dụ: tôi có thể tăng mẫu của mình để mã hiện tại của tôi chuyển từ chạy ở 0,17 giây lên 170 giây. Vâng, tôi muốn thử nghiệm ở các cỡ mẫu lớn hơn vì có ít biến thể hơn ở đó.
Flipper

2
"Đừng tối ưu hóa cho đến khi bạn biết bạn cần." Trừ khi bạn chỉ sử dụng một thành ngữ khác trên danh nghĩa và có thể tránh làm lại mã của bạn với ít nỗ lực hơn.
jeremyjjbrown

1
Một nơi bạn biết bạn cần là phỏng vấn (luôn luôn là thời gian tuyệt vời để tăng cường sự hiểu biết sâu sắc của bạn). Thật không may, tôi đã không tìm thấy bất kỳ bài viết hiện đại về điều này. (1) Chuỗi Java / C # có còn tệ trong năm 2017 không? (2) C ++ thì sao? (3) Bây giờ hãy nói về mới nhất và lớn nhất trong Python tập trung vào các trường hợp khi chúng ta cần thực hiện hàng triệu kết nối. Chúng ta có thể tin tưởng rằng tham gia sẽ làm việc trong thời gian tuyến tính?
dùng1854182

"Đủ nhanh" nghĩa là .join()gì? Câu hỏi chính là, nó có tạo ra một bản sao của chuỗi để nối (tương tự s = s + 'abc'), yêu cầu thời gian chạy O (n) hoặc b) chỉ cần nối vào chuỗi hiện có mà không tạo bản sao, yêu cầu O (1) ?
CGFoX

64

''.join(sequenceofstrings) là những gì thường hoạt động tốt nhất - đơn giản nhất và nhanh nhất.


3
@mshsayem, trong Python một chuỗi có thể là bất kỳ đối tượng nào, thậm chí là một hàm.
Nick Dandoulakis

2
Tôi hoàn toàn thích ''.join(sequence)thành ngữ này. Nó đặc biệt hữu ích để tạo các danh sách được phân tách bằng dấu phẩy: ', '.join([1, 2, 3])cung cấp chuỗi '1, 2, 3'.
Andrew Keeton

7
@mshsayem: "".join(chr(x) for x in xrange(65,91))--- trong trường hợp này, đối số để tham gia là một trình vòng lặp, được tạo thông qua biểu thức trình tạo. Không có danh sách tạm thời được xây dựng.
balpha

2
@balpha: và phiên bản trình tạo chậm hơn phiên bản hiểu danh sách: C: \ temp> python -mtimeit "'' .join (chr (x) cho x in xrange (65,91))" 100000 vòng, tốt nhất của 3: 9,71 usec mỗi vòng C: \ temp> python -mtimeit "'' .join ([chr (x) cho x in xrange (65,91)])" 100000 vòng, tốt nhất là 3: 7.1 usec mỗi vòng
hughdbrown

1
@hughdbrown, vâng, khi bạn có bộ nhớ trống, listcomp wazoo (trường hợp thời gian điển hình) có thể được tối ưu hóa tốt hơn genEx, thường là 20-30%. Tuy nhiên, khi mọi thứ chặt chẽ của bộ nhớ là khác nhau - khó có thể tái tạo theo thời gian! -)
Alex Martelli

58

Python 3.6 đã thay đổi trò chơi để nối chuỗi các thành phần đã biết bằng Nội suy chuỗi ký tự .

Đưa ra trường hợp thử nghiệm từ câu trả lời của mkoistinen , có chuỗi

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

Các ứng cử viên là

  • f'http://{domain}/{lang}/{path}'- 0.151

  • 'http://%s/%s/%s' % (domain, lang, path) - 0,321

  • 'http://' + domain + '/' + lang + '/' + path - 0,336

  • ''.join(('http://', domain, '/', lang, '/', path))- 0,249 Khuynh hướng (lưu ý rằng việc xây dựng một tuple có độ dài không đổi nhanh hơn một chút so với xây dựng một danh sách có độ dài không đổi).

Do đó, hiện tại mã ngắn nhất và đẹp nhất có thể cũng nhanh nhất.

Trong các phiên bản alpha của Python 3.6, việc triển khai f''chuỗi là chậm nhất có thể - thực tế mã byte được tạo ra tương đối giống ''.join()với trường hợp với các cuộc gọi không cần thiết str.__format__mà không có đối số sẽ trả vềself không . Những sự không hiệu quả đã được giải quyết trước 3.6 cuối cùng.

Tốc độ có thể tương phản với phương pháp nhanh nhất cho Python 2, đó là +ghép nối trên máy tính của tôi; và rằng mất 0,203 ms với chuỗi 8-bit, và 0,259 ms nếu chuỗi là tất cả Unicode.


38

Nó phụ thuộc vào những gì bạn đang làm.

Sau Python 2.5, nối chuỗi với toán tử + khá nhanh. Nếu bạn chỉ ghép một vài giá trị, sử dụng toán tử + hoạt động tốt nhất:

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

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

Tuy nhiên, nếu bạn kết hợp một chuỗi thành một vòng lặp, tốt hơn hết bạn nên sử dụng phương thức nối danh sách:

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

... nhưng lưu ý rằng bạn phải tập hợp một số chuỗi tương đối cao trước khi sự khác biệt trở nên đáng chú ý.


2
1) Trong phép đo đầu tiên của bạn, có lẽ việc xây dựng danh sách cần có thời gian. Hãy thử với một tuple. 2) CPython hoạt động tốt đồng đều, tuy nhiên các triển khai Python khác hoạt động kém hơn với + và + =
u0b34a0f6ae

22

Theo câu trả lời của John Fouhy, đừng tối ưu hóa trừ khi bạn phải làm thế, nhưng nếu bạn ở đây và hỏi câu hỏi này, điều đó có thể chính xác là do bạn phải làm . Trong trường hợp của tôi, tôi cần lắp ráp một số URL từ các biến chuỗi ... nhanh. Tôi nhận thấy không ai (cho đến nay) dường như đang xem xét phương pháp định dạng chuỗi, vì vậy tôi nghĩ tôi đã thử nó và, chủ yếu là vì lợi ích nhẹ, tôi nghĩ rằng tôi đã ném toán tử nội suy chuỗi vào đó để kiểm tra tốt. Thành thật mà nói, tôi đã không nghĩ một trong hai thứ này sẽ xếp chồng lên hoạt động '+' trực tiếp hoặc '' .join (). Nhưng đoán xem? Trên hệ thống Python 2.7.5 của tôi, toán tử nội suy chuỗi quy tắc tất cả chúng và string.format () là trình diễn tệ nhất:

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

Kết quả:

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

Nếu tôi sử dụng tên miền ngắn hơn và đường dẫn ngắn hơn, phép nội suy vẫn thắng. Sự khác biệt là rõ rệt hơn, mặc dù, với chuỗi dài hơn.

Bây giờ tôi đã có một kịch bản thử nghiệm đẹp, tôi cũng đã thử nghiệm theo Python 2.6, 3.3 và 3.4, đây là kết quả. Trong Python 2.6, toán tử cộng là nhanh nhất! Trên Python 3, tham gia thắng. Lưu ý: các thử nghiệm này rất lặp lại trên hệ thống của tôi. Vì vậy, 'cộng' luôn nhanh hơn trên 2.6, 'intp' luôn nhanh hơn trên 2.7 và 'tham gia' luôn nhanh hơn trên Python 3.x.

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

Bài học kinh nghiệm:

  • Đôi khi, giả định của tôi là chết sai.
  • Kiểm tra đối với hệ thống env. bạn sẽ được sản xuất
  • Nội suy chuỗi chưa chết!

tl; dr:

  • Nếu bạn sử dụng 2.6, hãy sử dụng toán tử +.
  • nếu bạn đang sử dụng 2.7, hãy sử dụng toán tử '%'.
  • nếu bạn đang sử dụng 3.x, hãy sử dụng '' .join ().

2
Lưu ý: phép nội suy chuỗi ký tự vẫn nhanh hơn cho 3.6+:f'http://{domain}/{lang}/{path}'
TemporalWolf

1
Ngoài ra, .format()có ba hình thức, theo thứ tự từ nhanh đến chậm: "{}".format(x), "{0}".format(x),"{x}".format(x=x)
TemporalWolf

Bài học thực sự: khi miền vấn đề của bạn nhỏ, ví dụ: soạn các chuỗi ngắn, phương thức thường không thành vấn đề. Và ngay cả khi nó quan trọng, ví dụ bạn thực sự đang xây dựng một triệu chuỗi, thì chi phí thường quan trọng hơn. Đó là một triệu chứng điển hình của việc lo lắng về vấn đề sai. Chỉ khi chi phí không đáng kể, ví dụ: khi xây dựng toàn bộ sách dưới dạng chuỗi, sự khác biệt về phương thức mới bắt đầu có vấn đề.
Hui Zhou

7

nó phụ thuộc khá nhiều vào kích thước tương đối của chuỗi mới sau mỗi lần ghép mới. Với +toán tử, với mỗi phép nối, một chuỗi mới được tạo. Nếu các chuỗi trung gian tương đối dài, +càng ngày càng chậm vì chuỗi trung gian mới đang được lưu trữ.

Hãy xem xét trường hợp này:

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

Các kết quả

1 0,00493192672729

2 0,000509023666382

3 0,00042200088501

4 0.000482797622681

Trong trường hợp 1 & 2, chúng tôi thêm một chuỗi lớn và tham gia () thực hiện nhanh hơn khoảng 10 lần. Trong trường hợp 3 & 4, chúng tôi thêm một chuỗi nhỏ và '+' thực hiện nhanh hơn một chút


3

Tôi gặp phải một tình huống mà tôi cần phải có một chuỗi có thể nối thêm có kích thước không xác định. Đây là các kết quả điểm chuẩn (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

Điều này dường như cho thấy rằng '+ =' là nhanh nhất. Các kết quả từ liên kết skymind là một chút lỗi thời.

(Tôi nhận ra rằng ví dụ thứ hai chưa hoàn tất, danh sách cuối cùng sẽ cần phải được tham gia. Tuy nhiên, điều này chỉ cho thấy rằng việc chuẩn bị danh sách mất nhiều thời gian hơn so với chuỗi concat.)


Tôi đang nhận được thời gian phụ 1 giây cho các bài kiểm tra thứ 3 và thứ 4. Tại sao bạn nhận được thời gian cao như vậy? pastebin.com/qabNMCHS
bad_keypoint

@ronnieaka: Anh ấy nhận được thời gian 1 giây cho tất cả các bài kiểm tra. Anh ấy nhận được> 1 khúc cho lần thứ 3 & 4, điều mà bạn không làm được. Tôi cũng nhận được thời gian chậm hơn trong các thử nghiệm đó (trên Python 2.7.5, Linux). Có thể là CPU, phiên bản, xây dựng cờ, ai biết được.
Thanatos

Những kết quả điểm chuẩn là vô ích. Đặc biệt, trường hợp đầu tiên, không thực hiện bất kỳ nối chuỗi nào, chỉ trả lại nguyên giá trị chuỗi thứ hai.
Antti Haapala

3

Một năm sau, chúng ta hãy kiểm tra câu trả lời của mkoistinen với python 3.4.3:

  • cộng 0,963564149000 (nhanh 95,83%)
  • tham gia 0.923408469000 (nhanh chóng 100%)
  • mẫu 1.501130934000 (nhanh 61,51%)
  • intp 1.019677452000 (nhanh 90,56%)

Không có gì thay đổi. Tham gia vẫn là phương pháp nhanh nhất. Với intp được cho là sự lựa chọn tốt nhất về khả năng đọc, bạn vẫn có thể muốn sử dụng intp.


1
Có lẽ nó có thể là một bổ sung cho câu trả lời mkoistinen vì nó hơi thiếu một câu trả lời đầy đủ (hoặc ít nhất là thêm mã bạn đang sử dụng).
Trilarion

1

Lấy cảm hứng từ điểm chuẩn của @ JasonBaker, đây là một chuỗi đơn giản so sánh 10 "abcdefghijklmnopqrstuvxyz"chuỗi, cho thấy .join()tốc độ nhanh hơn; ngay cả với sự gia tăng nhỏ này của các biến:

Catenation

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

Tham gia

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

Hãy xem câu trả lời được chấp nhận (kéo xuống từ lâu) của câu hỏi này: stackoverflow.com/questions/1349311/
mẹo

1

Đối với một tập hợp nhỏ các chuỗi ngắn (nghĩa là 2 hoặc 3 chuỗi không quá một vài ký tự), cộng vẫn là cách nhanh hơn. Sử dụng tập lệnh tuyệt vời của mkoistinen trong Python 2 và 3:

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)

Vì vậy, khi mã của bạn đang thực hiện một số lượng lớn các kết nối nhỏ riêng biệt, cộng với là cách ưa thích nếu tốc độ là rất quan trọng.


1

Có lẽ "chuỗi f mới trong Python 3.6" là cách nối chuỗi hiệu quả nhất.

Sử dụng% s

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

Sử dụng .format

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

Sử dụng f

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

Nguồn: https://realpython.com/python-f-strings/

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.