มีเหตุผลใดที่จะไม่ใช้ '+' เพื่อเชื่อมสองสตริงเข้าด้วยกัน?


124

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

อย่างไรก็ตามฉันเคยได้ยินมาว่า ( รวมถึงที่นี่ใน Stack Overflow ) ว่าคุณไม่ควรใช้+สำหรับการต่อสายอักขระ แต่ควรใช้''.joinหรือใช้สตริงรูปแบบแทน ฉันไม่เข้าใจว่าเหตุใดจึงเป็นเช่นนี้หากคุณเชื่อมต่อเพียงสองสตริง ถ้าความเข้าใจของฉันถูกต้องก็ไม่ควรใช้เวลาในการกำลังสองและผมคิดว่าa + bเป็นที่สะอาดและอ่านได้มากขึ้นกว่าหรือ''.join((a, b))'%s%s' % (a, b)

เป็นแนวทางปฏิบัติที่ดีที่จะใช้+เพื่อเชื่อมสองสตริงเข้าด้วยกันหรือไม่? หรือมีปัญหาที่ฉันไม่ทราบ?


มันดูดีกว่าและคุณสามารถควบคุมได้มากขึ้นที่จะไม่ทำการต่อกัน แต่การทุบตีสตริงช้าลงเล็กน้อย: P
Jakob Bowyer

คุณบอกว่า+เร็วขึ้นหรือช้าลง? และทำไม?
Taymon

1
+ เร็วกว่า, 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 และอื่น ๆ : "การสตริงไม่ดี" อาร์กิวเมนต์มีเกือบไม่มีอะไรจะทำอย่างไรกับความเร็ว __str__แต่การใช้ประโยชน์จากแปลงชนิดอัตโนมัติด้วย ดูคำตอบของฉันสำหรับตัวอย่าง
Izkata

คำตอบ:


120

ไม่มีอะไรผิดในการเชื่อมสองสตริงเข้าด้วย+กัน ''.join([a, b])อันที่จริงมันง่ายต่อการอ่านกว่า

คุณพูดถูกแม้ว่าการเชื่อมต่อมากกว่า 2 สตริงด้วยกัน+นั้นเป็นการดำเนินการ O (n ^ 2) (เทียบกับ O (n) สำหรับjoin) และทำให้ไม่มีประสิทธิภาพ อย่างไรก็ตามสิ่งนี้ไม่เกี่ยวข้องกับการใช้ลูป คู่a + b + c + ...คือ O (n ^ 2) เหตุผลที่การเรียงต่อกันทำให้เกิดสตริงใหม่

CPython2.4 ขึ้นไปพยายามลดปัญหานั้น แต่ยังแนะนำให้ใช้joinเมื่อเชื่อมต่อมากกว่า 2 สตริง


5
@Mutant: สามารถ.joinทำซ้ำได้ดังนั้นทั้งสองอย่าง.join([a,b])และ.join((a,b))ถูกต้อง
Foundling

1
คำแนะนำการกำหนดเวลาที่น่าสนใจในการใช้+หรือ+=ในคำตอบที่ยอมรับ (จากปี 2013) ที่stackoverflow.com/a/12171382/378826 (จาก Lennart Regebro) แม้กระทั่งสำหรับ CPython 2.3+ และเลือกเฉพาะรูปแบบ "ผนวก / เข้าร่วม" หากสิ่งที่ชัดเจนกว่านี้เปิดเผย ความคิดสำหรับการแก้ปัญหาในมือ
Dilettant

49

ตัวดำเนินการ Plus เป็นโซลูชันที่ดีอย่างสมบูรณ์แบบในการเชื่อมต่อสตริง Python สองสตริง แต่ถ้าคุณเพิ่มมากกว่าสองสตริง (n> 25) ไปเรื่อย ๆ คุณอาจต้องคิดอย่างอื่น

''.join([a, b, c]) เคล็ดลับคือการเพิ่มประสิทธิภาพ


2
tuple จะดีกว่ารายการไม่ใช่เหรอ?
ThiefMaster

7
ทูเพิลจะเร็วขึ้น - โค้ดเป็นเพียงตัวอย่าง :) โดยปกติแล้วอินพุตสตริงหลายตัวที่ยาวจะเป็นแบบไดนามิก
Mikko Ohtamaa

5
@martineau ฉันคิดว่าเขาหมายถึงการสร้างและสร้างappend()สตริงในรายการแบบไดนามิก
Peter C

5
ต้องพูดที่นี่: ทูเปิลมักเป็นโครงสร้างที่ช้าลงโดยเฉพาะอย่างยิ่งถ้ามันกำลังเติบโต ด้วยรายการคุณสามารถใช้ list.extend (list_of_items) และ list.append (item) ซึ่งเร็วกว่ามากเมื่อเชื่อมต่อสิ่งต่างๆแบบไดนามิก
Antti Haapala

6
+1 สำหรับn > 25. มนุษย์ต้องการจุดอ้างอิงเพื่อเริ่มต้นที่ไหนสักแห่ง
n611x007

8

สมมติฐานที่ว่าไม่ควรเคยใช้ + สำหรับการต่อสตริง แต่ใช้ ".join แทนเสมออาจเป็นตำนาน เป็นความจริงที่ว่าการใช้+สร้างสำเนาชั่วคราวที่ไม่จำเป็นของอ็อบเจ็กต์สตริงที่ไม่เปลี่ยนรูปแบบ แต่ข้อเท็จจริงอื่น ๆ ที่ไม่ได้ยกมาก็คือการเรียกjoinแบบวนซ้ำโดยทั่วไปจะเพิ่มค่าโสหุ้ยของfunction call. ลองใช้ตัวอย่างของคุณ

สร้างสองรายการรายการหนึ่งมาจากคำถาม SO ที่เชื่อมโยงและอีกรายการหนึ่งที่มีขนาดใหญ่กว่า

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

ให้สร้างสองฟังก์ชันUseJoinและUsePlusใช้ตามลำดับjoinและ+ฟังก์ชันการทำงาน

>>> 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)]

ให้รัน timeit กับรายการแรก

>>> 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
>>> 

มีรันไทม์เกือบเหมือนกัน

ให้ใช้ 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}

และดูเหมือนว่าการใช้ Join จะทำให้เกิดการเรียกใช้ฟังก์ชันที่ไม่จำเป็นซึ่งอาจเพิ่มค่าใช้จ่าย

ตอนนี้กลับมาที่คำถาม เราควรกีดกันการใช้+มากกว่าjoinในทุกกรณีหรือไม่?

ฉันเชื่อว่าไม่ควรคำนึงถึงสิ่งต่างๆ

  1. ความยาวของสตริงในคำถาม
  2. ไม่มีการดำเนินการเชื่อมต่อ

และนอกหลักสูตรในการเพิ่มประสิทธิภาพก่อนวัยพัฒนาเป็นสิ่งที่ชั่วร้าย


7
แน่นอนว่าแนวคิดนี้จะไม่ใช้joinภายในลูป แต่การวนซ้ำจะสร้างลำดับที่จะถูกส่งต่อเพื่อเข้าร่วม
jsbueno

7

เมื่อทำงานกับคนหลายคนบางครั้งก็ยากที่จะรู้ว่าเกิดอะไรขึ้น การใช้สตริงรูปแบบแทนการเชื่อมต่อสามารถหลีกเลี่ยงความรำคาญโดยเฉพาะอย่างยิ่งที่เกิดขึ้นกับเรามากมาย:

สมมติว่าฟังก์ชันต้องการอาร์กิวเมนต์และคุณเขียนโดยคาดหวังว่าจะได้สตริง:

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

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

ดังนั้นฟังก์ชันนี้อาจใช้บ่อยตลอดโค้ด เพื่อนร่วมงานของคุณอาจรู้แน่ชัดว่ามันทำอะไร แต่ไม่จำเป็นต้องเร่งความเร็วเต็มที่ในการทำงานภายในและอาจไม่ทราบว่าฟังก์ชันนั้นต้องการสตริง ดังนั้นพวกเขาอาจจบลงด้วยสิ่งนี้:

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

จะไม่มีปัญหาถ้าคุณใช้สตริงรูปแบบ:

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

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

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

เช่นเดียวกับออบเจ็กต์ทุกประเภทที่กำหนด__str__ซึ่งอาจส่งผ่านได้เช่นกัน:

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

ใช่: หากคุณสามารถใช้สตริงรูปแบบให้ทำได้และใช้ประโยชน์จากสิ่งที่ Python นำเสนอ


1
+1 สำหรับความคิดเห็นที่ไม่เห็นด้วยอย่างมีเหตุผล ผมยังคิดว่าผมเข้าข้าง+แม้ว่า
Taymon

1
ทำไมคุณไม่กำหนดวิธีการ foo เป็น: print 'bar:' + str (zeta)?
EngineerWithJava54321

@ EngineerWithJava54321 ตัวอย่างเช่นzeta = u"a\xac\u1234\u20ac\U00008000"คุณต้องใช้print 'bar: ' + unicode(zeta)เพื่อให้แน่ใจว่าไม่มีข้อผิดพลาด %sทำถูกต้องโดยไม่ต้องคิดถึงมันและสั้นกว่ามาก
Izkata

@ EngineerWithJava54321 ตัวอย่างอื่น ๆ มีความเกี่ยวข้องน้อยกว่าที่นี่ แต่ตัวอย่างเช่น"bar: %s"อาจได้รับการแปลเป็น"zrb: %s br"ภาษาอื่น %sรุ่นจะทำงานเพียง แต่รุ่นสตริง concat จะกลายเป็นระเบียบเพื่อจัดการทุกกรณีและนักแปลของคุณตอนนี้จะมีสองคำแปลที่แยกต่างหากเพื่อจัดการกับ
Izkata

หากพวกเขาไม่ทราบว่าการใช้งานของ foo คืออะไรพวกเขาจะพบข้อผิดพลาดdefนี้
insidesin

3

ฉันได้ทำการทดสอบอย่างรวดเร็ว:

import sys

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

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

และหมดเวลา:

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

เห็นได้ชัดว่ามีการเพิ่มประสิทธิภาพสำหรับa = a + bกรณี ไม่แสดงเวลา O (n ^ 2) อย่างที่อาจสงสัย

ดังนั้นอย่างน้อยในแง่ของประสิทธิภาพการใช้งาน+ก็ใช้ได้


3
คุณสามารถเปรียบเทียบกับกรณี "เข้าร่วม" ได้ที่นี่ และมีเรื่องของการใช้งาน Python อื่น ๆ เช่น pypy, jython, ironpython เป็นต้น ...
jsbueno

3

ตามเอกสาร Python การใช้ str.join () จะทำให้คุณมีความสม่ำเสมอของประสิทธิภาพในการใช้งาน Python ต่างๆ แม้ว่า CPython จะปรับพฤติกรรมกำลังสองของ s = s + t ให้เหมาะสม แต่การใช้งาน Python อื่น ๆ ก็ไม่อาจทำได้

รายละเอียดการใช้งาน CPython : หาก s และ t เป็นทั้งสองสตริงการใช้งาน Python บางอย่างเช่น CPython มักจะสามารถทำการเพิ่มประสิทธิภาพแบบแทนที่สำหรับการกำหนดรูปแบบ s = s + t หรือ s + = t เมื่อทำได้การเพิ่มประสิทธิภาพนี้จะทำให้เวลาทำงานกำลังสองมีโอกาสน้อยลง การเพิ่มประสิทธิภาพนี้ขึ้นอยู่กับทั้งเวอร์ชันและการใช้งาน สำหรับโค้ดที่ไวต่อประสิทธิภาพควรใช้วิธี str.join () ซึ่งรับรองประสิทธิภาพการเชื่อมต่อเชิงเส้นที่สอดคล้องกันในเวอร์ชันและการนำไปใช้งาน

ประเภทลำดับในเอกสาร Python (ดูเชิงอรรถ [6])



0

'' .join ([a, b])เป็นวิธีการแก้ดีกว่า+

เนื่องจากโค้ดควรเขียนในลักษณะที่ไม่เสียเปรียบการใช้งาน Python อื่น ๆ (PyPy, Jython, IronPython, Cython, Psyco และอื่น ๆ )

รูปแบบ + b = หรือ = A + B จะเปราะบางแม้ใน CPython และไม่อยู่ในที่ทั้งหมดในการใช้งานที่ไม่ได้ใช้ refcounting (อ้างอิงนับเป็นเทคนิคในการจัดเก็บจำนวนการอ้างอิงตัวชี้หรือมือจับไปที่ ทรัพยากรเช่นวัตถุบล็อกหน่วยความจำพื้นที่ดิสก์หรือทรัพยากรอื่น ๆ )

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


1
a += bทำงานในการใช้งานของงูหลามก็เพียงว่าในบางส่วนของพวกเขาจะต้องใช้เวลาเป็นกำลังสองเมื่อดำเนินการเสร็จภายในห่วง ; คำถามเกี่ยวกับการต่อสายอักขระนอกลูป
Taymon
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.