แสดงรายการความเข้าใจโดยไม่มี [] ใน Python


86

การเข้าร่วมรายการ:

>>> ''.join([ str(_) for _ in xrange(10) ])
'0123456789'

join ต้องทำซ้ำได้

เห็นได้ชัดว่าjoin's อาร์กิวเมนต์เป็น[ str(_) for _ in xrange(10) ]และมันเป็นความเข้าใจรายการ

ดูนี่สิ:

>>>''.join( str(_) for _ in xrange(10) )
'0123456789'

ตอนนี้joinอาร์กิวเมนต์เป็นเพียงstr(_) for _ in xrange(10)ไม่[]แต่ผลลัพธ์ก็เหมือนกัน

ทำไม? ไม่str(_) for _ in xrange(10)ยังผลิตรายการหรือ iterable หรือไม่?


1
ฉันคิดว่าjoinน่าจะเขียนด้วย C ดังนั้นจึงทำงานได้เร็วกว่าความเข้าใจในรายการมาก ... เวลาทดสอบ!
Joel Cornett

เห็นได้ชัดว่าฉันอ่านคำถามของคุณผิดทั้งหมด ดูเหมือนว่าจะคืนเครื่องกำเนิดไฟฟ้าให้ฉัน ...
Joel Cornett

18
หมายเหตุ: _ไม่มีความหมายพิเศษ แต่เป็นชื่อตัวแปรทั่วไป มักใช้เป็นชื่อทิ้ง แต่ไม่ใช่กรณีนี้ (คุณกำลังใช้ตัวแปร) ฉันจะหลีกเลี่ยงการใช้รหัสนี้ (อย่างน้อยก็ด้วยวิธีนี้)
rplnt

คำตอบ:


69
>>>''.join( str(_) for _ in xrange(10) )

นี้เรียกว่าการแสดงออกของเครื่องกำเนิดไฟฟ้าและมีการอธิบายในPEP 289

ความแตกต่างที่สำคัญระหว่างนิพจน์ตัวสร้างและความเข้าใจรายการคืออดีตไม่สร้างรายการในหน่วยความจำ

โปรดทราบว่ามีวิธีที่สามในการเขียนนิพจน์:

''.join(map(str, xrange(10)))

1
อย่างที่ฉันรู้มันเครื่องกำเนิดไฟฟ้าสามารถสร้างได้ด้วยนิพจน์ที่เหมือนทูเพิลเช่น( str(_) for _ in xrange(10) ). แต่ฉันก็งงว่าทำไม()สามารถละไว้joinได้ซึ่งหมายความว่ารหัสควรเป็นแบบ "" .join ((str (_) สำหรับ _ ใน xrange (10))) ใช่ไหม
Alcott

2
@Alcott ความเข้าใจเกี่ยวกับทูเปิลของฉันคือจริงๆแล้วมันถูกกำหนดโดยรายการนิพจน์ที่คั่นด้วยเครื่องหมายจุลภาคไม่ใช่ในวงเล็บ วงเล็บจะมีไว้เพื่อจัดกลุ่มค่าในงานที่กำหนดด้วยสายตาหรือเพื่อจัดกลุ่มค่าหากทูเปิลอยู่ในรายการที่คั่นด้วยจุลภาคอื่น ๆ เช่นการเรียกใช้ฟังก์ชัน สิ่งนี้มักแสดงให้เห็นโดยการรันโค้ดเช่นtup = 1, 2, 3; print(tup). ด้วยเหตุนี้การใช้forเป็นส่วนหนึ่งของนิพจน์จะสร้างตัวสร้างและวงเล็บจะอยู่ที่นั่นเพื่อแยกความแตกต่างจากลูปที่เขียนไม่ถูกต้อง
เอริคเอ็ดโลห์มาร์

133

ผู้ตอบคนอื่นตอบถูกว่าคุณได้ค้นพบนิพจน์ตัวสร้าง (ซึ่งมีสัญกรณ์คล้ายกับรายการความเข้าใจ แต่ไม่มีวงเล็บเหลี่ยมล้อมรอบ)

โดยทั่วไป Genexps (ตามที่รู้จักกันในชื่อ) มีประสิทธิภาพในการจำมากกว่าและเร็วกว่าการเข้าใจรายการ

อย่างไรก็ตามในกรณีของ''.join()ความเข้าใจในรายการนั้นทั้งเร็วกว่าและมีประสิทธิภาพในการจำมากกว่า เหตุผลก็คือการเข้าร่วมจำเป็นต้องทำการส่งผ่านข้อมูลสองครั้งดังนั้นจึงจำเป็นต้องมีรายการจริง ถ้าคุณให้มันก็สามารถเริ่มงานได้ทันที หากคุณให้ genexp แทนจะไม่สามารถเริ่มทำงานได้จนกว่าจะสร้างรายการใหม่ในหน่วยความจำโดยเรียกใช้ genexp เพื่อทำให้หมดแรง:

~ $ python -m timeit '"".join(str(n) for n in xrange(1000))'
1000 loops, best of 3: 335 usec per loop
~ $ python -m timeit '"".join([str(n) for n in xrange(1000)])'
1000 loops, best of 3: 288 usec per loop

ผลลัพธ์เดียวกันเมื่อเปรียบเทียบitertools.imapกับmap :

~ $ python -m timeit -s'from itertools import imap' '"".join(imap(str, xrange(1000)))'
1000 loops, best of 3: 220 usec per loop
~ $ python -m timeit '"".join(map(str, xrange(1000)))'
1000 loops, best of 3: 212 usec per loop

4
@lazyr จังหวะที่สองของคุณทำงานมากเกินไป อย่าพัน genexp ไว้รอบ listcomp - เพียงแค่ใช้ genexp โดยตรง ไม่น่าแปลกใจที่คุณมีเวลาแปลก ๆ
Raymond Hettinger

11
คุณช่วยอธิบายได้ไหมว่าทำไม''.join()ต้องใช้ 2 ผ่านตัววนซ้ำเพื่อสร้างสตริง
ovgolovin

28
@ovgolovin ฉันเดาว่าพาสแรกคือการรวมความยาวของสตริงเพื่อที่จะสามารถจัดสรรจำนวนหน่วยความจำที่ถูกต้องสำหรับสตริงที่ต่อกันในขณะที่พาสที่สองคือการคัดลอกสตริงแต่ละสตริงลงในพื้นที่ที่จัดสรร
Lauritz V. Thaulow

20
@lazyr การคาดเดานั้นถูกต้อง นั่นคือสิ่งที่ str.join ทำ :-)
Raymond Hettinger

4
บางครั้งฉันก็พลาดความสามารถในการ "ชื่นชอบ" คำตอบเฉพาะของ SO
ออกอากาศ

5

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

ในขณะที่มันเกิดขึ้นตัวสร้างรายการจะใช้สิ่งที่ทำซ้ำได้อย่างมีความสุขรวมถึงนิพจน์ตัวสร้าง ดังนั้น:

[str(n) for n in xrange(10)]

เป็นเพียง "syntactic sugar" สำหรับ:

list(str(n) for n in xrange(10))

กล่าวอีกนัยหนึ่งความเข้าใจในรายการเป็นเพียงนิพจน์กำเนิดที่เปลี่ยนเป็นรายการ


2
แน่ใจหรือว่าเทียบเท่าภายใต้ประทุน? Timeit พูดว่า: [str(x) for x in xrange(1000)]262 usec ,: list(str(x) for x in xrange(1000))304 usec.
Lauritz V. Thaulow

2
@lazyr คุณพูดถูก ความเข้าใจในรายการเร็วขึ้น และนี่คือสาเหตุที่ความเข้าใจในรายการรั่วไหลใน Python 2.x. นี่คือสิ่งที่ GVR เขียนว่า: "" นี่คือสิ่งประดิษฐ์ของการดำเนินการตามรายการดั้งเดิม มันเป็นหนึ่งใน "ความลับเล็ก ๆ น้อย ๆ ที่สกปรก" ของ Python มาหลายปีแล้ว มันเริ่มต้นจากการประนีประนอมโดยเจตนาเพื่อสร้างความเข้าใจในรายการอย่างรวดเร็วและถึงแม้จะไม่ใช่ข้อผิดพลาดทั่วไปสำหรับผู้เริ่มต้น แต่ก็มีการต่อยคนเป็นครั้งคราว " python-history.blogspot.com/2010/06/…
ovgolovin

3
@ovgolovin สาเหตุที่ listcomp เร็วขึ้นก็เพราะว่าjoinต้องสร้าง list ก่อนถึงจะเริ่มงานได้ "การรั่วไหล" ที่คุณอ้างถึงไม่ใช่ปัญหาด้านความเร็ว แต่หมายความว่าตัวแปรการเหนี่ยวนำลูปถูกเปิดเผยนอก listcomp
Raymond Hettinger

1
@RaymondHettinger แล้วคำเหล่านี้แปลว่าอะไร "มันเริ่มต้นจากการเป็น ประนีประนอมโดยเจตนาเพื่อให้เข้าใจรายการอย่างรวดเร็ว "? ตามที่ฉันเข้าใจมีความเชื่อมโยงของการรั่วไหลของพวกเขากับปัญหาความเร็ว GVR ยังเขียน: "สำหรับการแสดงออกกำเนิดเราไม่สามารถทำเช่นนี้แสดงออกเครื่องกำเนิดไฟฟ้าจะดำเนินการโดยใช้เครื่องกำเนิดไฟฟ้าที่มีการดำเนินการต้องมีกรอบการดำเนินการแยกต่างหากดังนั้น.. การแสดงออกกำเนิด (โดยเฉพาะอย่างยิ่งถ้าพวกเขาย้ำกว่าลำดับสั้น) มีประสิทธิภาพน้อยกว่า comprehensions "
ovgolovin

4
@ovgolovin คุณก้าวกระโดดที่ไม่ถูกต้องจากรายละเอียดการใช้งาน listcomp ว่าทำไม str. เข้าร่วมดำเนินการในลักษณะนี้ หนึ่งในบรรทัดแรกในรหัส str.join คือseq = PySequence_Fast(orig, "");และนั่นคือเหตุผลเดียวที่ตัววนซ้ำทำงานช้ากว่ารายการหรือทูเปิลเมื่อเรียก str.join () คุณสามารถเริ่มต้นการแชทได้หากต้องการพูดคุยเพิ่มเติม (ฉันเป็นผู้เขียน PEP 289 ผู้สร้างรหัสตัวเลือก LIST_APPEND และผู้ที่ปรับแต่งรายการ () ตัวสร้างรายการให้เหมาะสมดังนั้นฉันจึงมี ความคุ้นเคยกับปัญหา)
Raymond Hettinger

5

เป็นที่กล่าวถึงก็เป็นแสดงออกกำเนิด

จากเอกสารประกอบ:

วงเล็บสามารถละเว้นในการโทรที่มีอาร์กิวเมนต์เดียวเท่านั้น ดูส่วนการโทรสำหรับรายละเอียด


4

หากอยู่ใน parens แต่ไม่ใช่วงเล็บแสดงว่าเป็นนิพจน์ของเครื่องกำเนิดไฟฟ้าในทางเทคนิค นิพจน์ Generator ถูกนำมาใช้ครั้งแรกใน Python 2.4

http://wiki.python.org/moin/Generators

ส่วนหลังจากการรวม( str(_) for _ in xrange(10) )คือนิพจน์ตัวกำเนิดโดยตัวมันเอง คุณสามารถทำสิ่งต่างๆเช่น:

mylist = (str(_) for _ in xrange(10))
''.join(mylist)

และมีความหมายเหมือนกับที่คุณเขียนในกรณีที่สองข้างต้นทุกประการ

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

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


1

นั่นเป็นตัวสร้างมากกว่าความเข้าใจในรายการ เครื่องกำเนิดไฟฟ้าก็สามารถทำซ้ำได้เช่นกัน แต่แทนที่จะสร้างรายการทั้งหมดก่อนจากนั้นจึงส่งต่อเพื่อเข้าร่วมจะส่งผ่านค่าแต่ละค่าใน xrange ทีละรายการซึ่งจะมีประสิทธิภาพมากกว่ามาก


0

อาร์กิวเมนต์สำหรับการjoinเรียกครั้งที่สองของคุณคือนิพจน์ตัวสร้าง มันสร้างซ้ำได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.