นิพจน์ตัวสร้างกับความเข้าใจของรายการ


411

เมื่อใดที่คุณควรใช้ตัวสร้างนิพจน์และเมื่อใดที่คุณควรใช้ list comprehensions ใน Python

# Generator expression
(x*2 for x in range(256))

# List comprehension
[x*2 for x in range(256)]

27
จะ[exp for x in iter]เป็นน้ำตาลได้list((exp for x in iter))หรือ หรือมีความแตกต่างในการดำเนินการ?
b0fh

1
มันคิดว่าฉันมีคำถามที่เกี่ยวข้องดังนั้นเมื่อใช้ผลตอบแทนเราสามารถใช้เพียงแค่นิพจน์ตัวกำเนิดจากฟังก์ชันหรือเราต้องใช้ผลตอบแทนสำหรับฟังก์ชั่นเพื่อกลับวัตถุกำเนิด?

28
@ b0fh ตอบกลับความคิดเห็นของคุณช้ามาก: ใน Python2 มีความแตกต่างเล็กน้อยตัวแปรลูปจะรั่วไหลออกจากรายการความเข้าใจในขณะที่นิพจน์ตัวสร้างจะไม่รั่วไหล เปรียบเทียบX = [x**2 for x in range(5)]; print xกับY = list(y**2 for y in range(5)); print yที่สองจะให้ข้อผิดพลาด ใน Python3 เข้าใจรายการย่อมเป็นน้ำตาลประโยคสำหรับการแสดงออกเครื่องกำเนิดไฟฟ้าป้อนให้กับlist()ที่คุณคาดว่าดังนั้นตัวแปร loop จะไม่รั่วไหลออก
Bas Swinckels

12
ผมขอแนะนำให้อ่านPEP 0289 สรุปโดย"PEP นี้แนะนำกำเนิดการแสดงออกเป็นที่มีประสิทธิภาพสูง, หน่วยความจำทั่วไปที่มีประสิทธิภาพของ comprehensions รายการและเครื่องกำเนิดไฟฟ้า" นอกจากนี้ยังมีตัวอย่างที่มีประโยชน์เมื่อใช้
icc97

5
@ icc97 ฉันมีงานปาร์ตี้มาแปดปีแล้วและลิงค์ PEP นั้นสมบูรณ์แบบ ขอบคุณที่ทำให้หาง่าย!
eenblam

คำตอบ:


283

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

def gen():
    return (something for something in get_some_stuff())

print gen()[:2]     # generators don't support indexing or slicing
print [5,6] + gen() # generators can't be added to lists

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

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


70
บางครั้งคุณต้องใช้เครื่องกำเนิดไฟฟ้า - ตัวอย่างเช่นถ้าคุณกำลังเขียน coroutines ด้วยการตั้งเวลาแบบร่วมมือโดยใช้ผลผลิต แต่ถ้าคุณทำอย่างนั้นคุณอาจจะไม่ถามคำถามนี้เลย)
ephemient

12
ฉันรู้ว่านี่เก่า แต่ฉันคิดว่ามันคุ้มค่าที่จะเห็นว่าเครื่องกำเนิดไฟฟ้า (และสามารถทำซ้ำได้) สามารถเพิ่มลงในรายการที่มีการขยาย: a = [1, 2, 3] b = [4, 5, 6] a.extend(b)- ตอนนี้จะเป็น [1, 2, 3, 4, 5, 6] (คุณสามารถเพิ่มบรรทัดใหม่ในความคิดเห็นได้ไหม?)
jarvisteve

12
@jarvisteve ตัวอย่างของคุณปฏิเสธคำที่คุณพูด นอกจากนี้ยังมีจุดที่ดีที่นี่ รายการสามารถขยายได้ด้วยเครื่องกำเนิดไฟฟ้า แต่ก็ไม่มีจุดที่จะทำให้เป็นเครื่องกำเนิดไฟฟ้าได้ เครื่องกำเนิดไฟฟ้าไม่สามารถขยายได้ด้วยรายการและเครื่องกำเนิดไฟฟ้าไม่สามารถตั้งค่าได้ a = (x for x in range(0,10)), b = [1,2,3]เช่น a.extend(b)โยนข้อยกเว้น b.extend(a)จะประเมินทั้งหมดของซึ่งในกรณีนี้ไม่มีจุดที่จะทำให้มันเป็นเครื่องกำเนิดไฟฟ้าในตอนแรก
ตำหนิ Victoroff

4
@SlaterTyranus คุณถูกต้อง 100% และฉันก็สนับสนุนคุณอย่างแม่นยำ อย่างไรก็ตามฉันคิดว่าความคิดเห็นของเขาเป็นคำตอบที่ไม่เป็นประโยชน์สำหรับคำถามของ OP เพราะจะช่วยให้ผู้ที่พบตัวเองที่นี่เพราะพวกเขาพิมพ์บางอย่างเช่น 'รวมตัวสร้างกับรายการเข้าใจ' ลงในเครื่องมือค้นหา
rbp

1
เหตุผลที่ใช้เครื่องกำเนิดไฟฟ้าซ้ำแล้วซ้ำอีกไม่ได้ (เช่นข้อกังวลของฉันเกี่ยวกับการขาดหน่วยความจำแทนที่ความกังวลของฉันเกี่ยวกับ "การดึง" ค่าหนึ่งครั้ง ) อาจยังคงใช้เมื่อทำการวนซ้ำหลายครั้ง? ฉันจะบอกว่ามันอาจทำให้รายการมีประโยชน์มากขึ้น แต่ไม่ว่าจะเพียงพอหรือไม่ที่จะเกินความกังวลของหน่วยความจำก็เป็นอย่างอื่น
Rob Grant

181

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


39
+1 สำหรับอนันต์ คุณไม่สามารถทำสิ่งนั้นกับรายการไม่ว่าคุณจะสนใจเรื่องประสิทธิภาพเพียงใด
พอลเดรเปอร์

คุณสามารถสร้างเครื่องกำเนิดไฟฟ้าที่ไม่มีที่สิ้นสุดโดยใช้วิธีความเข้าใจ
AnnanFay

5
@Annan เฉพาะเมื่อคุณมีสิทธิ์เข้าถึงตัวสร้างที่ไม่มีที่สิ้นสุดอีกแล้ว ยกตัวอย่างเช่นitertools.count(n)เป็นลำดับอนันต์ของจำนวนเต็มเริ่มต้นจาก n ดังนั้น(2 ** item for item in itertools.count(n))จะเป็นลำดับอนันต์อำนาจของราคาเริ่มต้นที่2 2 ** n
Kevin

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

102

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

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


2
นี่อาจเป็นหัวข้อนอกเรื่องเล็กน้อย แต่น่าเสียดายที่ "ไม่สามารถ googlable" ... "สิ่งสำคัญที่สุด" ในบริบทนี้คืออะไร ฉันไม่ใช่เจ้าของภาษาอังกฤษ ... :)
Guillermo Ares

6
@ GuillermoAres นี้เป็นผลโดยตรงของ "googling" สำหรับความหมายของสิ่งที่สำคัญยิ่งกว่าสิ่งอื่นใด สูงสุด
Sнаđошƒаӽ

1
ดังนั้นlistsเร็วกว่าgeneratorการแสดงออก? จากการอ่านคำตอบของ dF มันเจอว่ามันเป็นวิธีอื่น
Hassan Baig

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

59

จุดสำคัญคือความเข้าใจในรายการจะสร้างรายการใหม่ เครื่องกำเนิดสร้างวัตถุที่ทำซ้ำได้ซึ่งจะ "กรอง" วัสดุต้นฉบับในขณะที่คุณใช้บิต

ลองนึกภาพคุณมีไฟล์บันทึกขนาด 2TB ชื่อ "hugefile.txt" และคุณต้องการเนื้อหาและความยาวสำหรับทุกบรรทัดที่ขึ้นต้นด้วยคำว่า "ENTRY"

ดังนั้นคุณลองเริ่มต้นด้วยการเขียนรายการความเข้าใจ:

logfile = open("hugefile.txt","r")
entry_lines = [(line,len(line)) for line in logfile if line.startswith("ENTRY")]

ซึ่งจะทำให้ไฟล์ทั้งไฟล์ประมวลผลแต่ละบรรทัดและจัดเก็บบรรทัดที่ตรงกันในอาร์เรย์ของคุณ อาร์เรย์นี้อาจมีเนื้อหามากถึง 2TB นั่นเป็น RAM จำนวนมากและอาจไม่เป็นไปตามวัตถุประสงค์ของคุณ

ดังนั้นเราจึงสามารถใช้เครื่องกำเนิดไฟฟ้าเพื่อใช้ "ตัวกรอง" กับเนื้อหาของเรา ไม่มีข้อมูลถูกอ่านจนกว่าเราจะเริ่มทำซ้ำผลลัพธ์

logfile = open("hugefile.txt","r")
entry_lines = ((line,len(line)) for line in logfile if line.startswith("ENTRY"))

ยังไม่ได้อ่านบรรทัดเดียวจากไฟล์ของเรา ที่จริงแล้วเราต้องการกรองผลลัพธ์ของเราให้ดียิ่งขึ้น:

long_entries = ((line,length) for (line,length) in entry_lines if length > 80)

ยังไม่มีการอ่านอะไรเลย แต่ตอนนี้เราได้ระบุเครื่องกำเนิดไฟฟ้าสองตัวที่จะทำงานกับข้อมูลของเราตามที่เราต้องการ

ให้เขียนบรรทัดที่กรองแล้วของเราไปยังไฟล์อื่น:

outfile = open("filtered.txt","a")
for entry,length in long_entries:
    outfile.write(entry)

ตอนนี้เราอ่านไฟล์อินพุต ในขณะที่forวงของเรายังคงร้องขอสายเพิ่มเติมตัวlong_entriesสร้างต้องการบรรทัดจากตัวentry_linesสร้างโดยส่งคืนเฉพาะกลุ่มที่มีความยาวมากกว่า 80 อักขระ และในทางกลับกันentry_linesเครื่องกำเนิดไฟฟ้าจะขอบรรทัด (กรองตามที่ระบุ) จากตัวlogfileวนซ้ำซึ่งจะอ่านไฟล์

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


46

ประโยชน์ของนิพจน์ตัวสร้างคือใช้หน่วยความจำน้อยลงเนื่องจากไม่ได้สร้างรายการทั้งหมดในครั้งเดียว นิพจน์ตัวสร้างจะใช้ดีที่สุดเมื่อรายการนั้นเป็นตัวกลางเช่นการรวมผลลัพธ์หรือสร้าง dict จากผลลัพธ์

ตัวอย่างเช่น:

sum(x*2 for x in xrange(256))

dict( (k, some_func(k)) for k in some_list_of_keys )

ข้อดีคือมีรายการที่ไม่ได้สร้างขึ้นอย่างสมบูรณ์และทำให้มีการใช้หน่วยความจำเพียงเล็กน้อย (และควรจะเร็วกว่า)

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

ตัวอย่างเช่น:

reversed( [x*2 for x in xrange(256)] )

9
มีคำแนะนำที่ถูกต้องสำหรับคุณในภาษาที่นิพจน์เครื่องกำเนิดไฟฟ้ามีไว้เพื่อใช้ในลักษณะนั้น สูญเสียวงเล็บ! sum(x*2 for x in xrange(256))
u0b34a0f6ae

8
sortedและreversedทำงานได้ดีกับนิพจน์ตัวกำเนิดใด ๆ ที่รวมไว้
marr75

1
หากคุณสามารถใช้ 2.7 และสูงกว่าตัวอย่าง dict () จะดูดีกว่าเป็นความเข้าใจแบบ dict (PEP สำหรับที่เก่ากว่านั้นนิพจน์ตัวสร้าง PEP แต่ใช้เวลานานกว่านั้น)
Jürgen A. Erhard

14

เมื่อสร้างตัวสร้างขึ้นจากวัตถุที่ไม่แน่นอน (เช่นรายการ) โปรดทราบว่าตัวสร้างจะได้รับการประเมินสถานะของรายการ ณ เวลาที่ใช้ตัวสร้างไม่ใช่เวลาของการสร้างตัวสร้าง:

>>> mylist = ["a", "b", "c"]
>>> gen = (elem + "1" for elem in mylist)
>>> mylist.clear()
>>> for x in gen: print (x)
# nothing

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


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

4

บางครั้งคุณสามารถออกไปด้วยฟังก์ชั่นteeจากitertoolsมันจะส่งคืนตัววนซ้ำหลาย ๆ ตัวสำหรับตัวสร้างเดียวกันที่สามารถใช้งานได้อย่างอิสระ


4

ฉันใช้โมดูล Hadoop บะฉ่อ ฉันคิดว่านี่เป็นตัวอย่างที่ดีในการจดบันทึก:

import mincemeat

def mapfn(k,v):
    for w in v:
        yield 'sum',w
        #yield 'count',1


def reducefn(k,v): 
    r1=sum(v)
    r2=len(v)
    print r2
    m=r1/r2
    std=0
    for i in range(r2):
       std+=pow(abs(v[i]-m),2)  
    res=pow((std/r2),0.5)
    return r1,r2,res

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

Hadoop เป็นตัวอย่างที่ยอดเยี่ยมสำหรับการใช้ข้อดีทั้งหมดของเครื่องกำเนิดไฟฟ้า

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