Làm cách nào để nối thêm một chuỗi vào chuỗi khác trong Python?


594

Tôi muốn một cách hiệu quả để nối một chuỗi với chuỗi khác trong Python, ngoài cách sau.

var1 = "foo"
var2 = "bar"
var3 = var1 + var2

Có phương pháp tích hợp tốt nào để sử dụng không?


8
TL; DR: Nếu bạn chỉ tìm cách đơn giản để nối các chuỗi và bạn không quan tâm đến hiệu quả:"foo" + "bar" + str(3)
Andrew

Câu trả lời:


609

Nếu bạn chỉ có một tham chiếu đến một chuỗi và bạn nối một chuỗi khác đến cuối, CPython sẽ xử lý trường hợp đặc biệt này và cố gắng mở rộng chuỗi tại chỗ.

Kết quả cuối cùng là hoạt động được khấu hao O (n).

ví dụ

s = ""
for i in range(n):
    s+=str(i)

đã từng là O (n ^ 2), nhưng bây giờ nó là O (n).

Từ nguồn (byteobject.c):

void
PyBytes_ConcatAndDel(register PyObject **pv, register PyObject *w)
{
    PyBytes_Concat(pv, w);
    Py_XDECREF(w);
}


/* The following function breaks the notion that strings are immutable:
   it changes the size of a string.  We get away with this only if there
   is only one module referencing the object.  You can also think of it
   as creating a new string object and destroying the old one, only
   more efficiently.  In any case, don't use this if the string may
   already be known to some other part of the code...
   Note that if there's not enough memory to resize the string, the original
   string object at *pv is deallocated, *pv is set to NULL, an "out of
   memory" exception is set, and -1 is returned.  Else (on success) 0 is
   returned, and the value in *pv may or may not be the same as on input.
   As always, an extra byte is allocated for a trailing \0 byte (newsize
   does *not* include that), and a trailing \0 byte is stored.
*/

int
_PyBytes_Resize(PyObject **pv, Py_ssize_t newsize)
{
    register PyObject *v;
    register PyBytesObject *sv;
    v = *pv;
    if (!PyBytes_Check(v) || Py_REFCNT(v) != 1 || newsize < 0) {
        *pv = 0;
        Py_DECREF(v);
        PyErr_BadInternalCall();
        return -1;
    }
    /* XXX UNREF/NEWREF interface should be more symmetrical */
    _Py_DEC_REFTOTAL;
    _Py_ForgetReference(v);
    *pv = (PyObject *)
        PyObject_REALLOC((char *)v, PyBytesObject_SIZE + newsize);
    if (*pv == NULL) {
        PyObject_Del(v);
        PyErr_NoMemory();
        return -1;
    }
    _Py_NewReference(*pv);
    sv = (PyBytesObject *) *pv;
    Py_SIZE(sv) = newsize;
    sv->ob_sval[newsize] = '\0';
    sv->ob_shash = -1;          /* invalidate cached hash value */
    return 0;
}

Nó đủ dễ để xác minh theo kinh nghiệm.

$ python -m timeit -s "s = ''" "cho i trong xrange (10): s + = 'a'"
1000000 vòng, tốt nhất là 3: 1,85 usec mỗi vòng
$ python -m timeit -s "s = ''" "cho i trong xrange (100): s + = 'a'"
10000 vòng, tốt nhất là 3: 16,8 usec mỗi vòng
$ python -m timeit -s "s = ''" "cho i trong xrange (1000): s + = 'a'"
10000 vòng, tốt nhất là 3: 158 usec mỗi vòng
$ python -m timeit -s "s = ''" "cho i trong xrange (10000): s + = 'a'"
1000 vòng, tốt nhất là 3: 1,71 msec mỗi vòng
$ python -m timeit -s "s = ''" "cho i trong xrange (100000): s + = 'a'"
10 vòng, tốt nhất là 3: 14,6 msec mỗi vòng
$ python -m timeit -s "s = ''" "cho i trong xrange (1000000): s + = 'a'"
10 vòng, tốt nhất là 3: 173 msec mỗi vòng

Tuy nhiên, điều quan trọng cần lưu ý là tối ưu hóa này không phải là một phần của thông số Python. Đó chỉ là trong triển khai cPython theo như tôi biết. Thử nghiệm thực nghiệm tương tự trên pypy hoặc jython chẳng hạn có thể cho thấy hiệu suất O (n ** 2) cũ hơn.

$ pypy -m timeit -s "s = ''" "cho i trong xrange (10): s + = 'a'"
10000 vòng, tốt nhất là 3: 90,8 usec mỗi vòng
$ pypy -m timeit -s "s = ''" "cho i trong xrange (100): s + = 'a'"
1000 vòng, tốt nhất là 3: 896 usec mỗi vòng
$ pypy -m timeit -s "s = ''" "cho i trong xrange (1000): s + = 'a'"
100 vòng, tốt nhất là 3: 9.03 ms mỗi vòng
$ pypy -m timeit -s "s = ''" "cho i trong xrange (10000): s + = 'a'"
10 vòng, tốt nhất là 3: 89,5 msec mỗi vòng

Cho đến nay rất tốt, nhưng sau đó,

$ pypy -m timeit -s "s = ''" "cho i trong xrange (100000): s + = 'a'"
10 vòng, tốt nhất là 3: 12,8 giây mỗi vòng

ouch thậm chí còn tồi tệ hơn bậc hai. Vì vậy, pypy đang làm một cái gì đó hoạt động tốt với các chuỗi ngắn, nhưng hoạt động kém cho các chuỗi lớn hơn.


14
Hấp dẫn. "Bây giờ", ý bạn là Python 3.x?
Steve Tjoa

10
@Steve, Không. Ít nhất là trong 2,6 thậm chí 2,5
John La Rooy

8
Bạn đã trích dẫn PyString_ConcatAndDelchức năng nhưng bao gồm các bình luận cho _PyString_Resize. Ngoài ra, nhận xét không thực sự xác lập khiếu nại của bạn về Big-O
Winston Ewert

3
chúc mừng bạn đã khai thác một tính năng CPython sẽ làm cho mã thu thập dữ liệu trên các triển khai khác. Lời khuyên tệ.
Jean-François Fabre

4
KHÔNG sử dụng cái này. Pep8 tuyên bố rõ ràng: Mã phải được viết theo cách không gây bất lợi cho việc triển khai Python khác (PyPy, Jython, IronPython, Cython, Psyco, và sau đó đưa ra ví dụ cụ thể này như một thứ gì đó để tránh vì nó rất dễ hỏng."".join(str_a, str_b)
Eraw

287

Đừng tối ưu hóa sớm. Nếu bạn không có lý do gì để tin rằng có một nút cổ chai tốc độ gây ra bởi các chuỗi nối thì chỉ cần sử dụng ++=:

s  = 'foo'
s += 'bar'
s += 'baz'

Điều đó nói rằng, nếu bạn đang nhắm đến thứ gì đó như StringBuilder của Java, thì thành ngữ Python chính tắc là thêm các mục vào danh sách và sau đó sử dụng str.joinđể nối tất cả chúng vào cuối:

l = []
l.append('foo')
l.append('bar')
l.append('baz')

s = ''.join(l)

Tôi không biết ý nghĩa tốc độ của việc xây dựng chuỗi của bạn dưới dạng danh sách và sau đó .join () trong đó là gì, nhưng tôi thấy đó thường là cách sạch nhất. Tôi cũng đã thành công lớn với việc sử dụng ký hiệu% s trong một chuỗi cho công cụ tạo khuôn mẫu SQL mà tôi đã viết.
richo

25
@Richo Sử dụng .join hiệu quả hơn. Lý do là các chuỗi Python là bất biến, do đó, việc sử dụng s + = more sẽ liên tục phân bổ nhiều chuỗi lớn hơn liên tiếp. .join sẽ tạo ra chuỗi cuối cùng trong một lần từ các bộ phận cấu thành của nó.
Ben

5
@Ben, đã có một sự cải thiện đáng kể trong lĩnh vực này - xem câu trả lời của tôi
John La Rooy

41
str1 = "Hello"
str2 = "World"
newstr = " ".join((str1, str2))

Điều đó kết hợp str1 và str2 với một khoảng trắng làm dấu phân cách. Bạn cũng có thể làm "".join(str1, str2, ...). str.join()mất một lần lặp, vì vậy bạn phải đặt các chuỗi trong một danh sách hoặc một tuple.

Đó là về hiệu quả như nó được cho một phương pháp dựng sẵn.


Điều gì xảy ra, nếu str1 là empy? Khoảng trắng sẽ được thiết lập?
Jürgen K.

38

Đừng.

Đó là, đối với hầu hết các trường hợp, bạn nên tạo toàn bộ chuỗi trong một lần thay vì nối thêm vào một chuỗi hiện có.

Ví dụ: không làm: obj1.name + ":" + str(obj1.count)

Thay vào đó: sử dụng "%s:%d" % (obj1.name, obj1.count)

Điều đó sẽ dễ đọc hơn và hiệu quả hơn.


54
Tôi xin lỗi, không có gì dễ đọc hơn (chuỗi + chuỗi) như ví dụ đầu tiên, ví dụ thứ hai có thể hiệu quả hơn, nhưng không dễ đọc hơn
JqueryToAddNumbers 27/215

23
@ExceptionSlayer, chuỗi + chuỗi khá dễ theo dõi. Nhưng "<div class='" + className + "' id='" + generateUniqueId() + "'>" + message_text + "</div>", sau đó tôi thấy ít đọc và dễ bị lỗi hơn"<div class='{classname}' id='{id}'>{message_text}</div>".format(classname=class_name, message_text=message_text, id=generateUniqueId())
Winston Ewert

Điều này hoàn toàn không giúp ích gì khi những gì tôi đang cố gắng làm tương đương với "chuỗi. = Verifydata ()" của PHP / perl hoặc tương tự.
Shadur

@Shadur, quan điểm của tôi là bạn nên suy nghĩ lại, bạn có thực sự muốn làm một cái gì đó tương đương, hoặc là một cách tiếp cận hoàn toàn khác tốt hơn?
Winston Ewert

1
Và trong trường hợp này, câu trả lời cho câu hỏi đó là "Không, vì cách tiếp cận đó không bao gồm trường hợp sử dụng của tôi"
Shadur 23/2/2016

11

Python 3.6 cung cấp cho chúng ta chuỗi f , đây là một điều thú vị:

var1 = "foo"
var2 = "bar"
var3 = f"{var1}{var2}"
print(var3)                       # prints foobar

Bạn có thể làm hầu hết mọi thứ bên trong niềng răng xoăn

print(f"1 + 1 == {1 + 1}")        # prints 1 + 1 == 2

10

Nếu bạn cần thực hiện nhiều thao tác chắp thêm để xây dựng một chuỗi lớn, bạn có thể sử dụng StringIO hoặc cStringIO. Giao diện giống như một tập tin. tức là: bạn writenối thêm văn bản vào nó.

Nếu bạn chỉ nối thêm hai chuỗi thì chỉ cần sử dụng +.


9

nó thực sự phụ thuộc vào ứng dụng của bạn. Nếu bạn đang lặp lại hàng trăm từ và muốn nối tất cả chúng vào một danh sách, .join()thì tốt hơn. Nhưng nếu bạn kết hợp một câu dài, bạn nên sử dụng +=.


5

Về cơ bản, không có sự khác biệt. Xu hướng nhất quán duy nhất là Python dường như ngày càng chậm hơn với mọi phiên bản ... :(


Danh sách

%%timeit
x = []
for i in range(100000000):  # xrange on Python 2.7
    x.append('a')
x = ''.join(x)

Python 2.7

1 vòng lặp, tốt nhất là 3: 7,34 giây trên mỗi vòng lặp

Con trăn 3,4

1 vòng lặp, tốt nhất là 3: 7,99 s mỗi vòng lặp

Con trăn 3.5

1 vòng lặp, tốt nhất là 3: 8,48 giây mỗi vòng lặp

Python 3.6

1 vòng lặp, tốt nhất là 3: 9,93 giây trên mỗi vòng lặp


Chuỗi

%%timeit
x = ''
for i in range(100000000):  # xrange on Python 2.7
    x += 'a'

Python 2.7 :

1 vòng lặp, tốt nhất là 3: 7,41 s mỗi vòng lặp

Con trăn 3,4

1 vòng lặp, tốt nhất là 3: 9,08 giây trên mỗi vòng lặp

Con trăn 3.5

1 vòng lặp, tốt nhất là 3: 8,82 giây trên mỗi vòng lặp

Python 3.6

1 vòng lặp, tốt nhất là 3: 9,24 giây trên mỗi vòng lặp


2
Tôi đoán nó phụ thuộc. Tôi nhận 1.19 s992 mstương ứng trên Python2.7
John La Rooy

5

nối các chuỗi với hàm __add__

str = "Hello"
str2 = " World"
st = str.__add__(str2)
print(st)

Đầu ra

Hello World

4
str + str2vẫn còn ngắn hơn
Nik O'Lai

2
a='foo'
b='baaz'

a.__add__(b)

out: 'foobaaz'

1
Mã là tốt, nhưng nó sẽ giúp có một lời giải thích đi kèm. Tại sao sử dụng phương pháp này chứ không phải là các câu trả lời khác trên trang này?
cgmb

11
Sử dụng a.__add__(b)là giống hệt với văn bản a+b. Khi bạn nối các chuỗi bằng +toán tử, Python sẽ gọi __add__phương thức trên chuỗi ở phía bên trái truyền chuỗi bên phải làm tham số.
Addie
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.