การต่อสตริงกับการแทนที่สตริงใน Python


98

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

ตัวอย่างที่เป็นรูปธรรมเราควรจัดการกับการสร้าง URI ที่ยืดหยุ่นได้อย่างไร:

DOMAIN = 'http://stackoverflow.com'
QUESTIONS = '/questions'

def so_question_uri_sub(q_num):
    return "%s%s/%d" % (DOMAIN, QUESTIONS, q_num)

def so_question_uri_cat(q_num):
    return DOMAIN + QUESTIONS + '/' + str(q_num)

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


ตลกใน Ruby การแก้ไขสตริงโดยทั่วไปเร็วกว่าการเรียงต่อกัน ...
Keltia

คุณลืมส่งคืน "" เข้าร่วม ([DOMAIN, QUESTIONS, str (q_num)])
Jimmy

ฉันไม่ใช่ผู้เชี่ยวชาญเรื่อง Ruby แต่ฉันจะพนันได้เลยว่าการแก้ไขจะเร็วกว่าเพราะสตริงไม่แน่นอนใน Ruby สตริงเป็นลำดับที่ไม่เปลี่ยนรูปใน Python
gotgenes

1
เพียงแสดงความคิดเห็นเล็กน้อยเกี่ยวกับ URI URI ไม่เหมือนกับสตริง มี URI ดังนั้นคุณต้องระมัดระวังให้มากเมื่อคุณเชื่อมต่อหรือเปรียบเทียบ ตัวอย่าง: เซิร์ฟเวอร์ที่ส่งตัวแทนของตนผ่าน http บนพอร์ต 80 example.org (ไม่มี slah ต่อท้าย) example.org/ (slash) example.org:80/ (slah + port 80) คือ uri เดียวกัน แต่ไม่เหมือนกัน สตริง
karlcow

คำตอบ:


55

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

>>> import timeit
>>> def so_q_sub(n):
...  return "%s%s/%d" % (DOMAIN, QUESTIONS, n)
...
>>> so_q_sub(1000)
'http://stackoverflow.com/questions/1000'
>>> def so_q_cat(n):
...  return DOMAIN + QUESTIONS + '/' + str(n)
...
>>> so_q_cat(1000)
'http://stackoverflow.com/questions/1000'
>>> t1 = timeit.Timer('so_q_sub(1000)','from __main__ import so_q_sub')
>>> t2 = timeit.Timer('so_q_cat(1000)','from __main__ import so_q_cat')
>>> t1.timeit(number=10000000)
12.166618871951641
>>> t2.timeit(number=10000000)
5.7813972166853773
>>> t1.timeit(number=1)
1.103492206766532e-05
>>> t2.timeit(number=1)
8.5206360154188587e-06

>>> def so_q_tmp(n):
...  return "{d}{q}/{n}".format(d=DOMAIN,q=QUESTIONS,n=n)
...
>>> so_q_tmp(1000)
'http://stackoverflow.com/questions/1000'
>>> t3= timeit.Timer('so_q_tmp(1000)','from __main__ import so_q_tmp')
>>> t3.timeit(number=10000000)
14.564135316080637

>>> def so_q_join(n):
...  return ''.join([DOMAIN,QUESTIONS,'/',str(n)])
...
>>> so_q_join(1000)
'http://stackoverflow.com/questions/1000'
>>> t4= timeit.Timer('so_q_join(1000)','from __main__ import so_q_join')
>>> t4.timeit(number=10000000)
9.4431309007150048

10
คุณทำการทดสอบด้วยสตริงขนาดใหญ่จริงหรือไม่ (เช่น 100000 ตัวอักษร)
drnk

24

อย่าลืมเกี่ยวกับการแทนที่ชื่อ:

def so_question_uri_namedsub(q_num):
    return "%(domain)s%(questions)s/%(q_num)d" % locals()

4
รหัสนี้มีแนวทางการเขียนโปรแกรมที่ไม่ถูกต้องอย่างน้อย 2 ประการ: ความคาดหวังของตัวแปรส่วนกลาง (โดเมนและคำถามจะไม่ถูกประกาศภายในฟังก์ชัน) และการส่งผ่านตัวแปรมากกว่าที่จำเป็นไปยังฟังก์ชัน format () การลงคะแนนเนื่องจากคำตอบนี้สอนการเข้ารหัสที่ไม่ดี
jperelli

12

ระวังการเชื่อมสตริงเข้าด้วยกัน! ต้นทุนของการต่อสตริงเป็นสัดส่วนกับความยาวของผลลัพธ์ การวนรอบนำคุณตรงไปยังดินแดนแห่ง N-squared บางภาษาจะเพิ่มประสิทธิภาพการเชื่อมต่อกับสตริงที่จัดสรรล่าสุด แต่มีความเสี่ยงที่จะพึ่งพาคอมไพเลอร์เพื่อเพิ่มประสิทธิภาพอัลกอริทึมกำลังสองของคุณให้เป็นเชิงเส้น ดีที่สุดที่จะใช้ดั้งเดิม ( join?) ที่ใช้รายการสตริงทั้งหมดทำการจัดสรรครั้งเดียวและรวมเข้าด้วยกันทั้งหมดในครั้งเดียว


16
นั่นไม่ใช่ปัจจุบัน ใน python เวอร์ชันล่าสุดบัฟเฟอร์สตริงที่ซ่อนอยู่จะถูกสร้างขึ้นเมื่อคุณเชื่อมสตริงเข้าด้วยกันในลูป
Seun Osewa

5
@Seun: ใช่อย่างที่ฉันพูดไปบางภาษาจะปรับให้เหมาะสม แต่เป็นการฝึกฝนที่มีความเสี่ยง
Norman Ramsey

11

"เนื่องจากการต่อสายอักขระได้ช่วยเพิ่มประสิทธิภาพอย่างมาก ... "

หากเรื่องประสิทธิภาพเป็นสิ่งที่ควรรู้

อย่างไรก็ตามปัญหาด้านประสิทธิภาพที่ฉันพบไม่เคยเกิดขึ้นกับการทำงานของสตริง โดยทั่วไปฉันมีปัญหากับการดำเนินการ I / O การเรียงลำดับและ O ( n 2 ) ซึ่งเป็นปัญหาคอขวด

จนกว่าการทำงานของสตริงจะเป็นตัว จำกัด ประสิทธิภาพฉันจะยึดติดกับสิ่งที่ชัดเจน ส่วนใหญ่นั่นคือการแทนที่เมื่อมีหนึ่งบรรทัดหรือน้อยกว่าการต่อกันเมื่อมันสมเหตุสมผลและเครื่องมือเทมเพลต (เช่น Mako) เมื่อมีขนาดใหญ่


10

สิ่งที่คุณต้องการเชื่อมต่อ / แก้ไขและวิธีที่คุณต้องการจัดรูปแบบผลลัพธ์ควรกระตุ้นการตัดสินใจของคุณ

  • การแก้ไขสตริงช่วยให้คุณเพิ่มการจัดรูปแบบได้อย่างง่ายดาย ในความเป็นจริงเวอร์ชันการแก้ไขสตริงของคุณไม่ได้ทำเช่นเดียวกับเวอร์ชันการเชื่อมต่อของคุณ มันเพิ่มเครื่องหมายทับไปข้างหน้าพิเศษก่อนq_numพารามิเตอร์ ในการทำสิ่งเดียวกันคุณจะต้องเขียนreturn DOMAIN + QUESTIONS + "/" + str(q_num)ในตัวอย่างนั้น

  • การสอดแทรกช่วยให้จัดรูปแบบตัวเลขได้ง่ายขึ้น "%d of %d (%2.2f%%)" % (current, total, total/current)จะอ่านได้น้อยกว่ามากในรูปแบบการเรียงต่อกัน

  • การเชื่อมต่อจะมีประโยชน์เมื่อคุณไม่มีจำนวนรายการคงที่สำหรับ string-ize

นอกจากนี้โปรดทราบว่า Python 2.6 แนะนำการแก้ไขสตริงเวอร์ชันใหม่ที่เรียกว่าสตริงเท็มเพลต :

def so_question_uri_template(q_num):
    return "{domain}/{questions}/{num}".format(domain=DOMAIN,
                                               questions=QUESTIONS,
                                               num=q_num)

เทมเพลตสตริงถูกกำหนดให้แทนที่% -interpolation ในที่สุด แต่ฉันคิดว่าจะไม่เกิดขึ้นสักพัก


มันจะเกิดขึ้นทุกครั้งที่คุณตัดสินใจย้ายไปที่ python 3.0 นอกจากนี้โปรดดูความคิดเห็นของ Peter สำหรับข้อเท็จจริงที่ว่าคุณสามารถทำการแทนที่ด้วยชื่อด้วยตัวดำเนินการ% ได้
John Fouhy

"การเชื่อมต่อจะมีประโยชน์เมื่อคุณไม่มีจำนวนรายการคงที่สำหรับ string-ize" - คุณหมายถึงรายการ / อาร์เรย์? ในกรณีนั้นคุณไม่สามารถเข้าร่วม () ได้หรือไม่?
strager

"คุณแค่เข้าร่วม () พวกเขาไม่ได้หรือ" - ใช่ (สมมติว่าคุณต้องการตัวคั่นที่เหมือนกันระหว่างรายการ) ความเข้าใจในรายการและตัวสร้างทำงานได้ดีกับ string.join
Tim Lesher

1
"มันจะเกิดขึ้นทุกครั้งที่คุณตัดสินใจย้ายไปที่ python 3.0" - ไม่ py3k ยังคงสนับสนุนตัวดำเนินการ% จุดเลิกใช้งานถัดไปที่เป็นไปได้คือ 3.1 ดังนั้นจึงยังมีชีวิตอยู่
Tim Lesher

2
2 ปีต่อมา ... python 3.2 ใกล้จะเปิดตัวแล้วและ% style interpolation ก็ยังใช้ได้
Corey Goldberg

8

ฉันแค่ทดสอบความเร็วของวิธีการต่อ / การแทนที่สตริงแบบต่างๆด้วยความอยากรู้อยากเห็น การค้นหาโดย Google ในหัวข้อนี้ทำให้ฉันมาที่นี่ ฉันคิดว่าฉันจะโพสต์ผลการทดสอบของฉันด้วยความหวังว่าอาจช่วยให้ใครบางคนตัดสินใจได้

    import timeit
    def percent_():
            return "test %s, with number %s" % (1,2)

    def format_():
            return "test {}, with number {}".format(1,2)

    def format2_():
            return "test {1}, with number {0}".format(2,1)

    def concat_():
            return "test " + str(1) + ", with number " + str(2)

    def dotimers(func_list):
            # runs a single test for all functions in the list
            for func in func_list:
                    tmr = timeit.Timer(func)
                    res = tmr.timeit()
                    print "test " + func.func_name + ": " + str(res)

    def runtests(func_list, runs=5):
            # runs multiple tests for all functions in the list
            for i in range(runs):
                    print "----------- TEST #" + str(i + 1)
                    dotimers(func_list)

... หลังจากรันruntests((percent_, format_, format2_, concat_), runs=5)ฉันพบว่า% method เร็วกว่าวิธีอื่น ๆ ในสตริงเล็ก ๆ เหล่านี้ประมาณสองเท่า วิธี concat ช้าที่สุดเสมอ (แทบจะไม่) มีความแตกต่างเล็กน้อยมากเมื่อเปลี่ยนตำแหน่งในไฟล์format()วิธีนี้ แต่การสลับตำแหน่งมักจะช้ากว่าวิธีการจัดรูปแบบปกติอย่างน้อย. 01

ตัวอย่างผลการทดสอบ:

    test concat_()  : 0.62  (0.61 to 0.63)
    test format_()  : 0.56  (consistently 0.56)
    test format2_() : 0.58  (0.57 to 0.59)
    test percent_() : 0.34  (0.33 to 0.35)

ฉันเรียกใช้สิ่งเหล่านี้เนื่องจากฉันใช้การต่อสตริงในสคริปต์ของฉันและฉันสงสัยว่าค่าใช้จ่ายคืออะไร ฉันใช้คำสั่งต่างๆเพื่อให้แน่ใจว่าไม่มีสิ่งใดรบกวนหรือได้รับประสิทธิภาพที่ดีขึ้นเป็นอันดับแรกหรือครั้งสุดท้าย ในบันทึกด้านข้างฉันโยนเครื่องกำเนิดสตริงที่ยาวกว่าเข้าไปในฟังก์ชั่นเหล่านั้นเช่น"%s" + ("a" * 1024)และ concat ปกติเร็วกว่าการใช้formatและ%วิธีการ ฉันเดาว่ามันขึ้นอยู่กับสตริงและสิ่งที่คุณพยายามจะบรรลุ หากประสิทธิภาพมีความสำคัญจริง ๆ อาจเป็นการดีกว่าที่จะลองสิ่งต่างๆและทดสอบ ฉันมักจะเลือกความสามารถในการอ่านมากกว่าความเร็วเว้นแต่ความเร็วจะเป็นปัญหา แต่นั่นก็เป็นแค่ฉัน ดังนั้นไม่ชอบการคัดลอก / วางของฉันฉันต้องใส่ 8 ช่องว่างในทุกอย่างเพื่อให้มันดูถูกต้อง ฉันมักจะใช้ 4.


1
คุณควรพิจารณาอย่างจริงจังว่าคุณกำลังทำโปรไฟล์อย่างไร สำหรับหนึ่ง concat ของคุณช้าเพราะคุณมี str สองตัวอยู่ในนั้น เมื่อใช้สตริงผลลัพธ์จะตรงกันข้ามเนื่องจาก string concat เร็วกว่าทางเลือกทั้งหมดเมื่อมีเพียงสามสายเท่านั้นที่เกี่ยวข้อง
Justus Wingert

@JustusWingert ตอนนี้สองขวบแล้ว ฉันได้เรียนรู้มากมายตั้งแต่โพสต์ 'การทดสอบ' นี้ จริงๆแล้วทุกวันนี้ฉันใช้str.format()และstr.join()มากกว่าการเรียงต่อกันตามปกติ ฉันยังจับตาดู 'f-strings' จากPEP 498ซึ่งเพิ่งได้รับการยอมรับ สำหรับการstr()โทรที่มีผลต่อประสิทธิภาพฉันแน่ใจว่าคุณพูดถูก ฉันไม่รู้ว่าตอนนั้นการเรียกใช้ฟังก์ชันมีราคาแพงแค่ไหน ฉันยังคิดว่าควรทำการทดสอบเมื่อมีข้อสงสัย
Cj Welborn

หลังจากการทดสอบอย่างรวดเร็วjoin_(): return ''.join(["test ", str(1), ", with number ", str(2)])ดูเหมือนว่าjoinจะช้ากว่าเปอร์เซ็นต์ด้วย
พูดไม่ออก

4

โปรดจำไว้ว่าการตัดสินใจโวหารมีการตัดสินใจในทางปฏิบัติถ้าคุณเคยวางแผนในการรักษาหรือแก้จุดบกพร่องรหัสของคุณ :-) มีคำพูดที่มีชื่อเสียงจากนู (? อาจจะอ้างโฮร์) คือ: "เราควรจะลืมเกี่ยวกับประสิทธิภาพการขนาดเล็กที่พูดเกี่ยวกับ 97% ของเวลา: การเพิ่มประสิทธิภาพก่อนวัยอันควรเป็นรากเหง้าของความชั่วร้ายทั้งหมด "

ตราบเท่าที่คุณระมัดระวังไม่ (พูด) เปลี่ยนงาน O (n) ให้เป็นงาน O (n 2 ) ฉันจะไปกับสิ่งที่คุณเข้าใจง่ายที่สุด ..


0

ฉันใช้การเปลี่ยนตัวทุกที่ที่ทำได้ ฉันจะใช้การเรียงต่อกันถ้าฉันสร้างสตริงขึ้นมาเพื่อบอกว่า for-loop


7
"การสร้างสตริงใน for-loop" - มักเป็นกรณีที่คุณสามารถใช้ ".join และนิพจน์ตัวสร้าง ..
John Fouhy

-1

อันที่จริงสิ่งที่ถูกต้องที่จะทำในกรณีนี้ (สร้างเส้นทาง) os.path.joinคือการใช้งาน ไม่ใช่การต่อสตริงหรือการแก้ไข


1
ซึ่งเป็นจริงสำหรับเส้นทางระบบปฏิบัติการ (เช่นบนระบบไฟล์ของคุณ) แต่ไม่ใช่เมื่อสร้าง URI ดังตัวอย่างนี้ URI จะมี "/" เป็นตัวคั่นเสมอ
Andre Blum
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.