Bất kỳ lý do gì để không sử dụng '+' để nối hai chuỗi?


124

Một phản vật chất phổ biến trong Python là nối một chuỗi các chuỗi bằng cách sử dụng +trong một vòng lặp. Điều này thật tệ vì trình thông dịch Python phải tạo một đối tượng chuỗi mới cho mỗi lần lặp và nó sẽ mất thời gian bậc hai. (Các phiên bản gần đây của CPython rõ ràng có thể tối ưu hóa điều này trong một số trường hợp, nhưng các triển khai khác không thể, vì vậy các lập trình viên không nên dựa vào điều này.) ''.joinLà cách phù hợp để làm điều này.

Tuy nhiên, tôi đã nghe nó nói ( bao gồm cả ở đây trên Stack Overflow ) rằng bạn không bao giờ nên sử dụng +để nối chuỗi, mà thay vào đó hãy luôn sử dụng ''.joinhoặc một chuỗi định dạng. Tôi không hiểu tại sao lại xảy ra trường hợp này nếu bạn chỉ nối hai chuỗi. Nếu sự hiểu biết của tôi là đúng, nó sẽ không mất thời gian bậc hai, và tôi nghĩ a + bnó rõ ràng hơn và dễ đọc hơn là ''.join((a, b))hoặc '%s%s' % (a, b).

Có thực hành tốt để sử dụng +để nối hai chuỗi không? Hay có vấn đề gì mà tôi không biết?


Nó gọn gàng hơn và bạn có nhiều quyền kiểm soát hơn để không thực hiện nối. NHƯNG nó hơi chậm, thương mại chuỗi bashing off: P
Jakob Bowyer

Bạn đang nói +là nhanh hơn hay chậm hơn? Và tại sao?
Taymon

1
+ nhanh hơn, In [2]: %timeit "a"*80 + "b"*80 1000000 loops, best of 3: 356 ns per loop In [3]: %timeit "%s%s" % ("a"*80, "b"*80) 1000000 loops, best of 3: 907 ns per loop
Jakob Bowyer

4
In [3]: %timeit "%s%s" % (a, b) 1000000 loops, best of 3: 590 ns per loop In [4]: %timeit a + b 10000000 loops, best of 3: 147 ns per loop
Jakob Bowyer

1
@JakobBowyer và những người khác: Đối số "nối chuỗi là không tốt" hầu như không liên quan gì đến tốc độ, nhưng lợi dụng chuyển đổi kiểu tự động với __str__. Xem câu trả lời của tôi cho các ví dụ.
Izkata

Câu trả lời:


120

Không có gì sai khi nối hai chuỗi với +. Thật vậy, nó dễ đọc hơn ''.join([a, b]).

Bạn đúng mặc dù việc nối nhiều hơn 2 chuỗi với +là một phép toán O (n ^ 2) (so với O (n) cho join) và do đó trở nên không hiệu quả. Tuy nhiên điều này không liên quan đến việc sử dụng vòng lặp. Chẵn a + b + c + ...là O (n ^ 2), lý do là mỗi phép nối tạo ra một chuỗi mới.

CPython2.4 trở lên cố gắng giảm thiểu điều đó, nhưng bạn vẫn nên sử dụng joinkhi nối nhiều hơn 2 chuỗi.


5
@Mutant: .joincó giá trị có thể lặp lại, vì vậy cả hai .join([a,b]).join((a,b))đều hợp lệ.
đúc

1
Thời gian thú vị gợi ý cách sử dụng +hoặc +=trong câu trả lời được chấp nhận (từ năm 2013) tại stackoverflow.com/a/12171382/378826 (từ Lennart Regebro) ngay cả đối với CPython 2.3+ và chỉ chọn mẫu "nối thêm / tham gia" nếu điều này rõ ràng hơn cho thấy ý tưởng cho giải pháp vấn đề trong tầm tay.
Dilettant

49

Toán tử Plus là giải pháp hoàn toàn tốt để nối hai chuỗi Python. Nhưng nếu bạn tiếp tục thêm nhiều hơn hai chuỗi (n> 25), bạn có thể muốn nghĩ điều gì đó khác.

''.join([a, b, c]) mẹo là tối ưu hóa hiệu suất.


2
Một bộ tuple sẽ không tốt hơn một danh sách sao?
ThiefMaster

7
Tuple sẽ nhanh hơn - mã chỉ là một ví dụ :) Thông thường, nhiều đầu vào chuỗi dài là động.
Mikko Ohtamaa

5
@martineau Tôi nghĩ ý của anh ấy là tạo động và nhập append()chuỗi vào danh sách.
Peter C

5
Cần phải nói ở đây: tuple thường là cấu trúc SLOWER, đặc biệt nếu nó đang phát triển. Với danh sách, bạn có thể sử dụng list.extend (list_of_items) và list.append (item) nhanh hơn nhiều khi nối động nội dung.
Antti Haapala

6
+1 cho n > 25. Con người cần điểm tham chiếu để bắt đầu từ đâu đó.
n611x007

8

Giả định rằng không bao giờ được sử dụng + để nối chuỗi mà thay vào đó luôn sử dụng '' .join có thể là một huyền thoại. Đúng là việc sử dụng +tạo ra các bản sao tạm thời không cần thiết của đối tượng chuỗi bất biến nhưng một thực tế khác không được trích dẫn là việc gọi jointrong một vòng lặp thường sẽ thêm chi phí của function call. Hãy lấy ví dụ của bạn.

Tạo hai danh sách, một từ câu hỏi SO được liên kết và một danh sách khác được tạo ra lớn hơn

>>> myl1 = ['A','B','C','D','E','F']
>>> myl2=[chr(random.randint(65,90)) for i in range(0,10000)]

Cho phép tạo hai chức năng UseJoinUsePlussử dụng chức năng join+chức năng tương ứng .

>>> def UsePlus():
    return [myl[i] + myl[i + 1] for i in range(0,len(myl), 2)]

>>> def UseJoin():
    [''.join((myl[i],myl[i + 1])) for i in range(0,len(myl), 2)]

Cho phép thời gian chạy với danh sách đầu tiên

>>> myl=myl1
>>> t1=timeit.Timer("UsePlus()","from __main__ import UsePlus")
>>> t2=timeit.Timer("UseJoin()","from __main__ import UseJoin")
>>> print "%.2f usec/pass" % (1000000 * t1.timeit(number=100000)/100000)
2.48 usec/pass
>>> print "%.2f usec/pass" % (1000000 * t2.timeit(number=100000)/100000)
2.61 usec/pass
>>> 

Chúng có thời gian chạy gần như giống nhau.

Cho phép sử dụng cProfile

>>> myl=myl2
>>> cProfile.run("UsePlus()")
         5 function calls in 0.001 CPU seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.001    0.001    0.001    0.001 <pyshell#1376>:1(UsePlus)
        1    0.000    0.000    0.001    0.001 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {len}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        1    0.000    0.000    0.000    0.000 {range}


>>> cProfile.run("UseJoin()")
         5005 function calls in 0.029 CPU seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.015    0.015    0.029    0.029 <pyshell#1388>:1(UseJoin)
        1    0.000    0.000    0.029    0.029 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {len}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
     5000    0.014    0.000    0.014    0.000 {method 'join' of 'str' objects}
        1    0.000    0.000    0.000    0.000 {range}

Và có vẻ như việc sử dụng Tham gia, dẫn đến các lệnh gọi hàm không cần thiết có thể thêm vào chi phí.

Bây giờ trở lại câu hỏi. Có nên không khuyến khích việc sử dụng +over jointrong mọi trường hợp?

Tôi tin rằng không, mọi thứ nên được cân nhắc

  1. Độ dài của chuỗi trong câu hỏi
  2. Không có hoạt động kết hợp.

Và đi chệch hướng trong quá trình tối ưu hóa trước khi phát triển là điều xấu.


7
Tất nhiên, ý tưởng sẽ không sử dụng joinbên trong chính vòng lặp - thay vào đó, vòng lặp sẽ định vị một trình tự sẽ được chuyển qua để tham gia.
jsbueno

7

Khi làm việc với nhiều người, đôi khi rất khó để biết chính xác điều gì đang xảy ra. Sử dụng một chuỗi định dạng thay vì nối có thể tránh một sự phiền toái cụ thể đã xảy ra với chúng tôi rất nhiều lần:

Giả sử, một hàm yêu cầu một đối số và bạn viết nó với mong đợi nhận được một chuỗi:

In [1]: def foo(zeta):
   ...:     print 'bar: ' + zeta

In [2]: foo('bang')
bar: bang

Vì vậy, chức năng này có thể được sử dụng khá thường xuyên trong suốt mã. Đồng nghiệp của bạn có thể biết chính xác chức năng của nó, nhưng không nhất thiết phải cập nhật đầy đủ về nội bộ và có thể không biết rằng hàm mong đợi một chuỗi. Và vì vậy họ có thể kết thúc với điều này:

In [3]: foo(23)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)

/home/izkata/<ipython console> in <module>()

/home/izkata/<ipython console> in foo(zeta)

TypeError: cannot concatenate 'str' and 'int' objects

Sẽ không có vấn đề gì nếu bạn chỉ sử dụng một chuỗi định dạng:

In [1]: def foo(zeta):
   ...:     print 'bar: %s' % zeta
   ...:     
   ...:     

In [2]: foo('bang')
bar: bang

In [3]: foo(23)
bar: 23

Điều này cũng đúng cho tất cả các loại đối tượng xác định __str__, cũng có thể được chuyển vào:

In [1]: from datetime import date

In [2]: zeta = date(2012, 4, 15)

In [3]: print 'bar: ' + zeta
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)

/home/izkata/<ipython console> in <module>()

TypeError: cannot concatenate 'str' and 'datetime.date' objects

In [4]: print 'bar: %s' % zeta
bar: 2012-04-15

Vì vậy, có: Nếu bạn có thể sử dụng một chuỗi định dạng, hãy làm điều đó và tận dụng những gì Python cung cấp.


1
+1 cho một quan điểm bất đồng chính kiến ​​có lý do. Tôi vẫn nghĩ rằng tôi ủng hộ +mặc dù.
Taymon

1
Tại sao bạn không định nghĩa phương thức foo là: print 'bar:' + str (zeta)?
EngineerWithJava54321

@ EngineerWithJava54321 Ví dụ, zeta = u"a\xac\u1234\u20ac\U00008000"- vì vậy bạn phải sử dụng print 'bar: ' + unicode(zeta)để đảm bảo nó không bị lỗi. %sthực hiện nó ngay mà không cần phải suy nghĩ về nó, và ngắn hơn nhiều
Izkata

@ EngineerWithJava54321 Các ví dụ khác ít liên quan hơn ở đây, nhưng ví dụ: "bar: %s"có thể được dịch sang "zrb: %s br"một số ngôn ngữ khác. Các %sphiên bản sẽ chỉ làm việc, nhưng phiên bản chuỗi-concat sẽ trở thành một mớ hỗn độn để xử lý mọi trường hợp và dịch của bạn bây giờ sẽ có hai bản dịch riêng biệt để đối phó với
Izkata

Nếu họ không biết cách triển khai của foo là gì, họ sẽ gặp phải lỗi này với bất kỳ def.
insidesin 19/09/17

3

Tôi đã thực hiện một bài kiểm tra nhanh:

import sys

str = e = "a xxxxxxxxxx very xxxxxxxxxx long xxxxxxxxxx string xxxxxxxxxx\n"

for i in range(int(sys.argv[1])):
    str = str + e

và hẹn giờ nó:

mslade@mickpc:/binks/micks/ruby/tests$ time python /binks/micks/junk/strings.py  8000000
8000000 times

real    0m2.165s
user    0m1.620s
sys     0m0.540s
mslade@mickpc:/binks/micks/ruby/tests$ time python /binks/micks/junk/strings.py  16000000
16000000 times

real    0m4.360s
user    0m3.480s
sys     0m0.870s

Rõ ràng có một sự tối ưu hóa cho a = a + btrường hợp. Nó không thể hiện thời gian O (n ^ 2) như người ta có thể nghi ngờ.

Vì vậy, ít nhất về mặt hiệu suất, sử dụng +là ổn.


3
Bạn có thể so sánh với trường hợp "tham gia" ở đây. Và có vấn đề về các triển khai Python khác, chẳng hạn như pypy, jython, ironpython, v.v.
jsbueno

3

Theo tài liệu Python, việc sử dụng str.join () sẽ cung cấp cho bạn hiệu suất nhất quán trên các triển khai Python khác nhau. Mặc dù CPython tối ưu hóa hành vi bậc hai của s = s + t, các triển khai Python khác có thể không.

Chi tiết triển khai CPython : Nếu s và t là cả hai chuỗi, một số triển khai Python như CPython thường có thể thực hiện tối ưu hóa tại chỗ cho các bài tập có dạng s = s + t hoặc s + = t. Khi có thể, tối ưu hóa này làm cho thời gian chạy bậc hai ít có khả năng hơn nhiều. Tối ưu hóa này phụ thuộc vào cả phiên bản và cách triển khai. Đối với mã nhạy cảm về hiệu suất, bạn nên sử dụng phương thức str.join () để đảm bảo hiệu suất nối tuyến tính nhất quán giữa các phiên bản và triển khai.

Các kiểu trình tự trong tài liệu Python (xem phần ghi chú [6])


2

Tôi sử dụng phần sau với python 3.8

string4 = f'{string1}{string2}{string3}'

0

'' .join ([a, b]) là giải pháp tốt hơn + .

Bởi vì Mã phải được viết theo cách không gây bất lợi cho các triển khai khác của Python (PyPy, Jython, IronPython, Cython, Psyco, v.v.)

tạo thành một + = b hoặc a = a + b là mong manh ngay cả trong CPython và không có mặt ở tất cả trong hiện thực mà không sử dụng refcounting (đếm tham chiếu là một kỹ thuật lưu trữ số lượng tài liệu tham khảo, gợi ý, hoặc xử lý để một tài nguyên chẳng hạn như một đối tượng, khối bộ nhớ, không gian đĩa hoặc tài nguyên khác )

https://www.python.org/dev/peps/pep-0008/#programming-recommendations


1
a += bhoạt động trong tất cả các triển khai của Python, chỉ là trên một số chúng, cần thời gian bậc hai khi được thực hiện bên trong một vòng lặp ; câu hỏi về nối chuỗi bên ngoài một vòng lặp.
Taymon
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.