Nối chuỗi so với thay thế chuỗi trong Python


98

Trong Python, vị trí và thời điểm sử dụng nối chuỗi so với thay thế chuỗi không có tôi. Vì quá trình nối chuỗi đã chứng kiến ​​sự thúc đẩy lớn về hiệu suất, liệu đây có phải là một quyết định mang tính phong cách hơn là một quyết định thực tế?

Để có một ví dụ cụ thể, cách xử lý việc xây dựng các URI linh hoạt:

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)

Chỉnh sửa: Cũng đã có những đề xuất về việc tham gia một danh sách các chuỗi và sử dụng sự thay thế được đặt tên. Đây là những biến thể về chủ đề trung tâm, đó là, cách nào là Cách làm đúng để thực hiện vào thời điểm đó? Cảm ơn vì những câu trả lời!


Funny, trong Ruby, chuỗi suy thường là nhanh hơn so với nối ...
Keltia

bạn quên trả lại "" .join ([DOMAIN, QUESTIONS, str (q_num)])
Jimmy

Tôi không phải chuyên gia về Ruby, nhưng tôi dám cá rằng nội suy nhanh hơn vì các chuỗi có thể thay đổi trong Ruby. Chuỗi là chuỗi bất biến trong Python.
gotgenes

1
chỉ một chút nhận xét về URI. URI không hoàn toàn giống như chuỗi. Có các URI, vì vậy bạn phải rất cẩn thận khi nối hoặc so sánh chúng. Ví dụ: một máy chủ phân phối các đại diện của nó qua http trên cổng 80. example.org (không có slah ở cuối) example.org/ (chém) example.org:80/ (slah + cổng 80) giống nhau nhưng không giống nhau chuỗi.
karlcow

Câu trả lời:


55

Kết nối nhanh hơn (đáng kể) theo máy của tôi. Nhưng về mặt phong cách, tôi sẵn sàng trả giá bằng sự thay thế nếu hiệu suất không quá quan trọng. Vâng, và nếu tôi cần định dạng, thậm chí không cần đặt câu hỏi ... không có lựa chọn nào khác ngoài sử dụng nội suy / tạo mẫu.

>>> 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
bạn đã thực hiện kiểm tra với chuỗi lớn thực sự (như 100000 ký tự)?
drnk

24

Đừng quên về sự thay thế có tên:

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

4
Đoạn mã này có ít nhất 2 cách lập trình không tốt: kỳ vọng vào các biến toàn cục (miền và câu hỏi không được khai báo bên trong hàm) và chuyển nhiều biến hơn mức cần thiết vào một hàm format (). Từ chối vì câu trả lời này dạy các phương pháp mã hóa không tốt.
jperelli

12

Hãy cảnh giác với việc nối các chuỗi trong một vòng lặp! Chi phí nối chuỗi tỷ lệ thuận với độ dài của kết quả. Vòng lặp dẫn bạn đến thẳng vùng đất N-bình phương. Một số ngôn ngữ sẽ tối ưu hóa việc ghép nối với chuỗi được phân bổ gần đây nhất, nhưng sẽ rất rủi ro khi tin tưởng vào trình biên dịch để tối ưu hóa thuật toán bậc hai của bạn xuống tuyến tính. Tốt nhất là sử dụng nguyên thủy ( join?) Lấy toàn bộ danh sách các chuỗi, thực hiện một phân bổ duy nhất và nối tất cả chúng trong một lần.


16
Đó không phải là hiện tại. Trong phiên bản mới nhất của python, một bộ đệm chuỗi ẩn được tạo khi bạn nối các chuỗi trong một vòng lặp.
Seun Osewa

5
@Seun: Vâng, như tôi đã nói, một số ngôn ngữ sẽ tối ưu hóa, nhưng đó là một cách thực hành đầy rủi ro.
Norman Ramsey

11

"Vì quá trình nối chuỗi đã chứng kiến ​​sự thúc đẩy lớn về hiệu suất ..."

Nếu hiệu suất quan trọng, điều này là tốt để biết.

Tuy nhiên, các vấn đề về hiệu suất mà tôi đã thấy chưa bao giờ liên quan đến các hoạt động chuỗi. Tôi thường gặp rắc rối với I / O, sắp xếp và O ( n 2 ) là nút cổ chai.

Cho đến khi các hoạt động chuỗi là giới hạn hiệu suất, tôi sẽ gắn bó với những điều hiển nhiên. Hầu hết, đó là sự thay thế khi nó là một dòng hoặc ít hơn, nối khi nó có ý nghĩa và một công cụ mẫu (như Mako) khi nó lớn.


10

Những gì bạn muốn nối / nội suy và cách bạn muốn định dạng kết quả sẽ dẫn đến quyết định của bạn.

  • Nội suy chuỗi cho phép bạn dễ dàng thêm định dạng. Trên thực tế, phiên bản nội suy chuỗi của bạn không làm điều tương tự như phiên bản nối của bạn; nó thực sự thêm một dấu gạch chéo bổ sung trước q_numtham số. Để làm điều tương tự, bạn sẽ phải viết return DOMAIN + QUESTIONS + "/" + str(q_num)trong ví dụ đó.

  • Nội suy giúp định dạng số dễ dàng hơn; "%d of %d (%2.2f%%)" % (current, total, total/current)sẽ khó đọc hơn nhiều ở dạng nối.

  • Kết nối hữu ích khi bạn không có một số mục cố định cho chuỗi-ize.

Ngoài ra, hãy biết rằng Python 2.6 giới thiệu một phiên bản nội suy chuỗi mới, được gọi là tạo mẫu chuỗi :

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

String templating được dự kiến ​​cuối cùng sẽ thay thế% -interpolation, nhưng điều đó sẽ không xảy ra trong một thời gian khá lâu, tôi nghĩ vậy.


Chà, nó sẽ xảy ra bất cứ khi nào bạn quyết định chuyển sang python 3.0. Ngoài ra, hãy xem nhận xét của Peter để biết thực tế là bạn vẫn có thể thực hiện các thay thế được đặt tên với toán tử%.
John Fouhy

"Kết hợp hữu ích khi bạn không có một số mục cố định cho chuỗi-ize." - Ý bạn là một danh sách / mảng? Trong trường hợp đó, bạn không thể tham gia () họ?
strager

"Bạn không thể chỉ tham gia () họ?" - Có (giả sử bạn muốn phân cách đồng nhất giữa các mục). Khả năng hiểu danh sách và trình tạo hoạt động hiệu quả với string.join.
Tim Lesher

1
"Chà, nó sẽ xảy ra bất cứ khi nào bạn quyết định chuyển sang python 3.0" - Không, py3k vẫn hỗ trợ toán tử%. Điểm không thể chấp nhận tiếp theo là 3.1, vì vậy nó vẫn còn tồn tại.
Tim Lesher

2
2 năm sau ... python 3.2 sắp phát hành và nội suy theo kiểu% vẫn ổn.
Corey Goldberg

8

Tôi chỉ đang thử nghiệm tốc độ của các phương pháp nối / thay thế chuỗi khác nhau vì tò mò. Một tìm kiếm trên google về chủ đề này đã đưa tôi đến đây. Tôi nghĩ rằng tôi sẽ đăng kết quả kiểm tra của mình với hy vọng rằng nó có thể giúp ai đó quyết định.

    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)

... Sau khi chạy runtests((percent_, format_, format2_, concat_), runs=5), tôi thấy rằng phương thức% nhanh gấp đôi so với các phương thức khác trên các chuỗi nhỏ này. Phương thức concat luôn là chậm nhất (hầu như không). Có sự khác biệt rất nhỏ khi chuyển đổi vị trí trongformat() phương pháp, nhưng chuyển đổi vị trí luôn chậm hơn ít nhất 0,01 so với phương pháp định dạng thông thường.

Mẫu kết quả kiểm tra:

    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)

Tôi đã chạy những điều này bởi vì tôi sử dụng nối chuỗi trong các tập lệnh của mình và tôi đã tự hỏi chi phí là bao nhiêu. Tôi đã chạy chúng theo các thứ tự khác nhau để đảm bảo không có gì can thiệp hoặc đạt được hiệu suất tốt hơn dù là đầu tiên hay cuối cùng. Một lưu ý nhỏ, tôi đã đưa vào một số trình tạo chuỗi dài hơn vào các hàm như vậy "%s" + ("a" * 1024)và concat thông thường nhanh hơn gần 3 lần (1.1 so với 2.8) so với sử dụng các phương thức format%. Tôi đoán nó phụ thuộc vào các chuỗi, và những gì bạn đang cố gắng đạt được. Nếu hiệu suất thực sự quan trọng, tốt hơn là bạn nên thử những thứ khác nhau và kiểm tra chúng. Tôi có xu hướng chọn khả năng đọc hơn tốc độ, trừ khi tốc độ trở thành vấn đề, nhưng đó chỉ là tôi. VẬY không thích sao chép / dán của tôi, tôi phải đặt 8 khoảng trắng trên mọi thứ để làm cho nó trông đúng. Tôi thường sử dụng 4.


1
Bạn nên nghiêm túc xem xét những gì bạn đang lập hồ sơ như thế nào. Đối với một concat của bạn chậm vì bạn có hai phôi str trong đó. Với chuỗi thì kết quả ngược lại, vì chuỗi nối thực sự nhanh hơn tất cả các lựa chọn thay thế khi chỉ liên quan đến ba chuỗi.
Justus Wingert

@JustusWingert, bây giờ là hai tuổi. Tôi đã học được rất nhiều điều kể từ khi đăng 'bài kiểm tra' này. Thành thật mà nói, những ngày này tôi sử dụng str.format()str.join()quá trình nối bình thường. Tôi cũng đang theo dõi 'f-string' từ PEP 498 , gần đây đã được chấp nhận. Đối với các str()cuộc gọi ảnh hưởng đến hiệu suất, tôi chắc chắn rằng bạn đúng về điều đó. Lúc đó tôi không biết hàm gọi đắt như thế nào. Tôi vẫn nghĩ rằng các xét nghiệm nên được thực hiện khi có bất kỳ nghi ngờ nào.
Cj Welborn

Sau khi kiểm tra nhanh với join_(): return ''.join(["test ", str(1), ", with number ", str(2)]), nó dường như joincũng chậm hơn phần trăm.
hào nhoáng

4

Hãy nhớ rằng, các quyết định theo phong cách những quyết định thực tế, nếu bạn có kế hoạch duy trì hoặc gỡ lỗi mã của mình :-) Có một câu nói nổi tiếng từ Knuth (có thể là trích dẫn Hoare?): "Chúng ta nên quên đi những hiệu quả nhỏ, hãy nói khoảng 97% thời gian: tối ưu hóa quá sớm là gốc rễ của mọi điều ác. "

Miễn là bạn cẩn thận không (nói) biến một nhiệm vụ O (n) thành một nhiệm vụ O (n 2 ), tôi sẽ chọn cách nào bạn thấy dễ hiểu nhất ..


0

Tôi sử dụng thay thế bất cứ nơi nào tôi có thể. Tôi chỉ sử dụng phép nối nếu tôi đang xây dựng một chuỗi trong vòng lặp for.


7
"xây dựng một chuỗi trong một cho vòng lặp" - thường đây là một trường hợp mà bạn có thể sử dụng '' .join và một biểu thức máy phát điện ..
John Fouhy

-1

Trên thực tế, điều chính xác cần làm, trong trường hợp này (xây dựng đường dẫn) là sử dụng os.path.join. Không phải nối chuỗi hoặc nội suy


1
điều đó đúng với đường dẫn hệ điều hành (như trên hệ thống tệp của bạn) nhưng không đúng khi xây dựng URI như trong ví dụ này. URI luôn có '/' làm dấu phân tách.
Andre Blum,
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.