ฉันจะผนวกสตริงหนึ่งไปยังอีกสตริงหนึ่งใน Python ได้อย่างไร?


594

ฉันต้องการวิธีที่มีประสิทธิภาพในการผนวกสตริงหนึ่งต่อไปยังอีกสตริงใน Python นอกเหนือจากที่กล่าวมา

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

มีวิธีการใช้ที่ดีในตัวหรือไม่?


8
TL; DR:ถ้าคุณเพียงแค่มองหาวิธีง่ายๆในการผนวกสตริงและคุณไม่สนใจประสิทธิภาพ:"foo" + "bar" + str(3)
Andrew

คำตอบ:


609

ถ้าคุณมีการอ้างอิงถึงสายอักขระเพียงอันเดียวและคุณต่อสายอื่นเข้ากับส่วนท้ายตอนนี้ 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 จึงทำบางสิ่งที่ทำงานได้ดีกับสตริงสั้น ๆ แต่ทำงานได้ไม่ดีสำหรับสตริงที่มีขนาดใหญ่กว่า


14
น่าสนใจ เมื่อ "ตอนนี้" คุณหมายถึง Python 3.x หรือไม่
Steve Tjoa

10
@ Steve เลขที่อย่างน้อยก็ใน 2.6 อาจ 2.5
John La Rooy

8
คุณได้อ้างถึงPyString_ConcatAndDelฟังก์ชัน แต่ได้รวมความคิดเห็น_PyString_Resizeไว้ นอกจากนี้ความคิดเห็นไม่ได้สร้างข้อเรียกร้องของคุณเกี่ยวกับ Big-O
Winston Ewert

3
ขอแสดงความยินดีกับการใช้ประโยชน์จากคุณสมบัติ CPython ที่จะทำให้การรวบรวมข้อมูลรหัสในการใช้งานอื่น ๆ คำแนะนำที่ไม่ดี
Jean-François Fabre

4
ห้ามใช้สิ่งนี้ Pep8 ระบุอย่างชัดเจน: ควรเขียนโค้ดในลักษณะที่ไม่ทำให้การใช้งานของ Python ด้อยลง (PyPy, Jython, IronPython, Cython, Psyco และอื่น ๆ ) จากนั้นให้ตัวอย่างเฉพาะนี้เป็นสิ่งที่ควรหลีกเลี่ยงเนื่องจากมันบอบบางใช้ดีกว่า"".join(str_a, str_b)
Eraw

287

อย่าปรับให้เหมาะสมก่อนเวลาอันควร ถ้าคุณมีเหตุผลที่จะเชื่อว่าไม่มีมีคอขวดความเร็วที่เกิดจาก 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)

ฉันไม่ทราบว่าความเร็วในการสร้างสตริงของคุณเป็นรายการอะไรแล้ว. join () กำลังทำอยู่ แต่ฉันคิดว่ามันเป็นวิธีที่สะอาดที่สุด ฉันยังประสบความสำเร็จอย่างยอดเยี่ยมด้วยการใช้เครื่องหมาย% s ภายในสตริงสำหรับเครื่องมือสร้างเทมเพลต SQL ที่ฉันเขียน
richo

25
@Richo การใช้. join นั้นมีประสิทธิภาพมากกว่า เหตุผลก็คือสตริง Python ไม่เปลี่ยนรูปดังนั้นการใช้ s + = more ซ้ำ ๆ จะจัดสรรสตริงที่มีขนาดใหญ่ขึ้นอย่างต่อเนื่อง . join จะสร้างสตริงสุดท้ายในหนึ่งไปจากส่วนที่เป็นส่วนประกอบ
เบ็น

5
@Ben มีการปรับปรุงที่สำคัญในพื้นที่นี้ - ดูคำตอบของฉัน
John La Rooy

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

นั่นรวม str1 และ str2 ด้วยช่องว่างเป็นตัวคั่น "".join(str1, str2, ...)นอกจากนี้คุณยังสามารถทำ str.join()ทำซ้ำได้ดังนั้นคุณจะต้องใส่สตริงในรายการหรือ tuple

นั่นเป็นเรื่องเกี่ยวกับประสิทธิภาพเท่าที่จะได้รับสำหรับวิธี builtin


จะเกิดอะไรขึ้นถ้า str1 เป็น empy ช่องว่างจะถูกตั้งค่าหรือไม่
Jürgen K.

38

อย่า

นั่นคือสำหรับกรณีส่วนใหญ่คุณจะดีกว่าที่จะสร้างสตริงทั้งหมดในครั้งเดียวแทนที่จะต่อท้ายสตริงที่มีอยู่

ตัวอย่างเช่นอย่าทำ: obj1.name + ":" + str(obj1.count)

ใช้แทน "%s:%d" % (obj1.name, obj1.count)

นั่นจะง่ายต่อการอ่านและมีประสิทธิภาพมากขึ้น


54
ฉันขอโทษไม่มีอะไรอ่านง่ายกว่า (สตริง + สตริง) เหมือนตัวอย่างแรกตัวอย่างที่สองอาจมีประสิทธิภาพมากกว่า แต่ไม่สามารถอ่านได้มากขึ้น
JqueryToAddNumbers

23
@ExceptionSlayer สตริง + string นั้นค่อนข้างง่ายที่จะติดตาม แต่"<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())
Winston Ewert

สิ่งนี้ไม่ได้ช่วยอะไรเลยเมื่อสิ่งที่ฉันพยายามทำคือสิ่งที่เทียบเท่าคร่าวๆว่า "สตริง" ของ PHP / perl = Verifydata () "หรือคล้ายกัน
Shadur

@Shadur ประเด็นของฉันคือคุณควรคิดอีกครั้งคุณต้องการทำอะไรที่เทียบเท่าหรือเป็นวิธีที่แตกต่างไปจากเดิมอย่างสิ้นเชิงหรือไม่?
Winston Ewert

1
และในกรณีนี้คำตอบสำหรับคำถามนั้นคือ "ไม่เพราะวิธีการดังกล่าวไม่ครอบคลุมกรณีการใช้งานของฉัน"
Shadur

11

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

10

หากคุณต้องการผนวกการดำเนินการจำนวนมากเพื่อสร้างสตริงขนาดใหญ่คุณสามารถใช้StringIOหรือ cStringIO อินเตอร์เฟสเป็นเหมือนไฟล์ เช่น: คุณwriteสามารถต่อท้ายข้อความ

+หากคุณเพียงแค่ท้ายสองสายจากนั้นเพียงแค่ใช้


9

มันขึ้นอยู่กับใบสมัครของคุณ หากคุณวนซ้ำคำหลายร้อยคำและต้องการผนวกพวกเขาทั้งหมดไว้ในรายการ.join()จะดีกว่า +=แต่ถ้าคุณกำลังวางกันเป็นประโยคยาวคุณดีกว่าการใช้


5

โดยทั่วไปไม่มีความแตกต่าง แนวโน้มที่สอดคล้องกันเพียงอย่างเดียวคือ 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


2
ฉันคิดว่ามันขึ้นอยู่กับ ฉันได้รับ1.19 sและ992 msตามลำดับใน Python2.7
John La Rooy

5

ต่อท้ายสตริงด้วยฟังก์ชัน__add__

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

เอาท์พุต

Hello World

4
str + str2ยังสั้นกว่า
Nik O'Lai

2
a='foo'
b='baaz'

a.__add__(b)

out: 'foobaaz'

1
รหัสเป็นสิ่งที่ดี แต่มันจะช่วยให้มีคำอธิบายประกอบ เหตุใดจึงใช้วิธีนี้แทนคำตอบอื่น ๆ ในหน้านี้
cgmb

11
ใช้เป็นเหมือนการเขียนa.__add__(b) a+bเมื่อคุณต่อสตริงเข้าด้วยกันโดยใช้+โอเปอเรเตอร์ Python จะเรียก__add__เมธอดบนสตริงทางด้านซ้ายผ่านสตริงด้านขวาเป็นพารามิเตอร์
Addie
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.