ฉันต้องการวิธีที่มีประสิทธิภาพในการผนวกสตริงหนึ่งต่อไปยังอีกสตริงใน Python นอกเหนือจากที่กล่าวมา
var1 = "foo"
var2 = "bar"
var3 = var1 + var2
มีวิธีการใช้ที่ดีในตัวหรือไม่?
ฉันต้องการวิธีที่มีประสิทธิภาพในการผนวกสตริงหนึ่งต่อไปยังอีกสตริงใน Python นอกเหนือจากที่กล่าวมา
var1 = "foo"
var2 = "bar"
var3 = var1 + var2
มีวิธีการใช้ที่ดีในตัวหรือไม่?
คำตอบ:
ถ้าคุณมีการอ้างอิงถึงสายอักขระเพียงอันเดียวและคุณต่อสายอื่นเข้ากับส่วนท้ายตอนนี้ CPython จะเป็นกรณีพิเศษและพยายามที่จะขยายสายอักขระให้เข้าที่
ผลลัพธ์ที่ได้คือการดำเนินการจะถูกตัดจำหน่าย O (n)
เช่น
s = ""
for i in range(n):
s+=str(i)
เคยเป็น O (n ^ 2) แต่ตอนนี้มันเป็น O (n)
จากแหล่งที่มา (bytesobject.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;
}
ง่ายพอที่จะตรวจสอบเชิงประจักษ์
$ python -m timeit -s "s = ''" สำหรับ i ใน xrange (10): s + = 'a' " 1000000 ลูปที่ดีที่สุดคือ 3: 1.85 usec ต่อลูป $ python -m timeit -s "s = ''" สำหรับ i ใน xrange (100): s + = 'a' " 10000 ลูปดีที่สุด 3: 16.8 usec ต่อลูป $ python -m timeit -s "s = ''" สำหรับ i ใน xrange (1,000): s + = 'a' " 10000 ลูปดีที่สุด 3: 158 usec ต่อลูป $ python -m timeit -s "s = ''" "สำหรับฉันใน xrange (10,000): s + = 'a'" 1000 ลูป, ดีที่สุดคือ 3: 1.71 msec ต่อลูป $ python -m timeit -s "s = ''" สำหรับ i ใน xrange (100000): s + = 'a' " 10 ลูปที่ดีที่สุดคือ 3: 14.6 msec ต่อลูป $ python -m timeit -s "s = ''" สำหรับ i ใน xrange (1000000): s + = 'a' " 10 ลูป, ดีที่สุดคือ 3: 173 msec ต่อลูป
อย่างไรก็ตามสิ่งสำคัญคือต้องทราบว่าการเพิ่มประสิทธิภาพนี้ไม่ได้เป็นส่วนหนึ่งของข้อมูลจำเพาะของ Python มันมีเฉพาะในการใช้ cPython เท่าที่ฉันรู้ การทดสอบเชิงประจักษ์เดียวกันใน pypy หรือ jython อาจแสดงประสิทธิภาพ O (n ** 2) ที่เก่ากว่า
$ pypy -m timeit -s "s = ''" "สำหรับฉันใน xrange (10): s + = 'a'" 10,000 ลูป, ดีที่สุดคือ 3: 90.8 usec ต่อลูป $ pypy -m timeit -s "s = ''" "สำหรับฉันใน xrange (100): s + = 'a'" 1,000 ลูปดีที่สุด 3: 896 usec ต่อลูป $ pypy -m timeit -s "s = ''" "สำหรับฉันใน xrange (1,000): s + = 'a'" 100 ลูป, ดีที่สุดคือ 3: 9.03 msec ต่อลูป $ pypy -m timeit -s "s = ''" "สำหรับฉันใน xrange (10,000): s + = 'a'" 10 ลูปที่ดีที่สุดคือ 3: 89.5 msec ต่อลูป
จนถึงตอนนี้ดี แต่แล้ว
$ pypy -m timeit -s "s = ''" "สำหรับฉันใน xrange (100000): s + = 'a'" 10 ลูปดีที่สุดใน 3: 12.8 วินาทีต่อลูป
อุซยิ่งเลวร้ายยิ่งกว่าสมการกำลังสอง ดังนั้น pypy จึงทำบางสิ่งที่ทำงานได้ดีกับสตริงสั้น ๆ แต่ทำงานได้ไม่ดีสำหรับสตริงที่มีขนาดใหญ่กว่า
PyString_ConcatAndDel
ฟังก์ชัน แต่ได้รวมความคิดเห็น_PyString_Resize
ไว้ นอกจากนี้ความคิดเห็นไม่ได้สร้างข้อเรียกร้องของคุณเกี่ยวกับ Big-O
"".join(str_a, str_b)
อย่าปรับให้เหมาะสมก่อนเวลาอันควร ถ้าคุณมีเหตุผลที่จะเชื่อว่าไม่มีมีคอขวดความเร็วที่เกิดจาก concatenations สตริงแล้วก็ติดกับ+
และ+=
:
s = 'foo'
s += 'bar'
s += 'baz'
ที่กล่าวว่าหากคุณกำลังมองหาสิ่งที่ต้องการ StringBuilder ของ Java, บัญญัติ Python idiom ของ Canon คือการเพิ่มรายการลงในรายการแล้วใช้str.join
เชื่อมต่อพวกเขาทั้งหมดในตอนท้าย:
l = []
l.append('foo')
l.append('bar')
l.append('baz')
s = ''.join(l)
str1 = "Hello"
str2 = "World"
newstr = " ".join((str1, str2))
นั่นรวม str1 และ str2 ด้วยช่องว่างเป็นตัวคั่น "".join(str1, str2, ...)
นอกจากนี้คุณยังสามารถทำ str.join()
ทำซ้ำได้ดังนั้นคุณจะต้องใส่สตริงในรายการหรือ tuple
นั่นเป็นเรื่องเกี่ยวกับประสิทธิภาพเท่าที่จะได้รับสำหรับวิธี builtin
อย่า
นั่นคือสำหรับกรณีส่วนใหญ่คุณจะดีกว่าที่จะสร้างสตริงทั้งหมดในครั้งเดียวแทนที่จะต่อท้ายสตริงที่มีอยู่
ตัวอย่างเช่นอย่าทำ: obj1.name + ":" + str(obj1.count)
ใช้แทน "%s:%d" % (obj1.name, obj1.count)
นั่นจะง่ายต่อการอ่านและมีประสิทธิภาพมากขึ้น
"<div class='" + className + "' id='" + generateUniqueId() + "'>" + message_text + "</div>"
ฉันพบว่าอ่านได้ง่ายและผิดพลาดน้อยกว่า"<div class='{classname}' id='{id}'>{message_text}</div>".format(classname=class_name, message_text=message_text, id=generateUniqueId())
Python 3.6 ทำให้เรามีสตริง fซึ่งเป็นสิ่งที่น่ายินดี:
var1 = "foo"
var2 = "bar"
var3 = f"{var1}{var2}"
print(var3) # prints foobar
คุณสามารถทำทุกอย่างภายในวงเล็บปีกกา
print(f"1 + 1 == {1 + 1}") # prints 1 + 1 == 2
หากคุณต้องการผนวกการดำเนินการจำนวนมากเพื่อสร้างสตริงขนาดใหญ่คุณสามารถใช้StringIOหรือ cStringIO อินเตอร์เฟสเป็นเหมือนไฟล์ เช่น: คุณwrite
สามารถต่อท้ายข้อความ
+
หากคุณเพียงแค่ท้ายสองสายจากนั้นเพียงแค่ใช้
มันขึ้นอยู่กับใบสมัครของคุณ หากคุณวนซ้ำคำหลายร้อยคำและต้องการผนวกพวกเขาทั้งหมดไว้ในรายการ.join()
จะดีกว่า +=
แต่ถ้าคุณกำลังวางกันเป็นประโยคยาวคุณดีกว่าการใช้
โดยทั่วไปไม่มีความแตกต่าง แนวโน้มที่สอดคล้องกันเพียงอย่างเดียวคือ Python ดูเหมือนว่าจะช้าลงในทุก ๆ รุ่น ... :(
%%timeit
x = []
for i in range(100000000): # xrange on Python 2.7
x.append('a')
x = ''.join(x)
Python 2.7
1 วนที่ดีที่สุดของ 3: 7.34 s ต่อวง
Python 3.4
1 วนที่ดีที่สุดของ 3: 7.99 s ต่อวง
Python 3.5
1 วนที่ดีที่สุดของ 3: 8.48 s ต่อวง
Python 3.6
1 loop, ดีที่สุดคือ 3: 9.93 s ต่อ loop
%%timeit
x = ''
for i in range(100000000): # xrange on Python 2.7
x += 'a'
Python 2.7 :
1 วนที่ดีที่สุดของ 3: 7.41 sต่อวง
Python 3.4
1 loop, ดีที่สุดคือ 3: 9.08 s ต่อ loop
Python 3.5
1 loop, ดีที่สุดคือ 3: 8.82 s ต่อ loop
Python 3.6
1 loop, ดีที่สุดคือ 3: 9.24 s ต่อ loop
1.19 s
และ992 ms
ตามลำดับใน Python2.7
ต่อท้ายสตริงด้วยฟังก์ชัน__add__
str = "Hello"
str2 = " World"
st = str.__add__(str2)
print(st)
เอาท์พุต
Hello World
str + str2
ยังสั้นกว่า
a='foo'
b='baaz'
a.__add__(b)
out: 'foobaaz'
a.__add__(b)
a+b
เมื่อคุณต่อสตริงเข้าด้วยกันโดยใช้+
โอเปอเรเตอร์ Python จะเรียก__add__
เมธอดบนสตริงทางด้านซ้ายผ่านสตริงด้านขวาเป็นพารามิเตอร์
"foo" + "bar" + str(3)