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?
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?
Câu trả lời:
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.
PyString_ConcatAndDel
chứ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
"".join(str_a, str_b)
Đừ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 +
và +=
:
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)
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.
Đừ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.
"<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())
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 write
nố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 +
.
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 ... :(
%%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
%%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
1.19 s
và 992 ms
tương ứng trên Python2.7
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
str + str2
vẫn còn ngắn hơn
a='foo'
b='baaz'
a.__add__(b)
out: 'foobaaz'
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ố.
"foo" + "bar" + str(3)