ฉันเริ่มที่จะเรียนรู้ Python และฉันได้พบกับฟังก์ชั่นเครื่องกำเนิด ฉันต้องการทราบว่าปัญหาประเภทใดที่หน้าที่เหล่านี้ดีในการแก้ไข
ฉันเริ่มที่จะเรียนรู้ Python และฉันได้พบกับฟังก์ชั่นเครื่องกำเนิด ฉันต้องการทราบว่าปัญหาประเภทใดที่หน้าที่เหล่านี้ดีในการแก้ไข
คำตอบ:
เครื่องกำเนิดไฟฟ้าให้การประเมินผลที่ขี้เกียจกับคุณ คุณใช้มันโดยวนซ้ำพวกมันอย่างชัดเจนด้วย 'for' หรือโดยปริยายโดยส่งผ่านไปยังฟังก์ชันใด ๆ หรือสร้างซ้ำ คุณสามารถคิดว่ากำเนิดเป็นคืนหลายรายการราวกับว่าพวกเขากลับรายการ แต่แทนที่จะคืนพวกเขาทั้งหมดในครั้งเดียวพวกเขากลับพวกเขาทีละคนและฟังก์ชั่นเครื่องกำเนิดไฟฟ้าจะหยุดชั่วคราวจนกว่าจะมีการร้องขอรายการถัดไป
เครื่องกำเนิดไฟฟ้าดีสำหรับการคำนวณชุดผลลัพธ์ขนาดใหญ่ (โดยเฉพาะอย่างยิ่งการคำนวณที่เกี่ยวข้องกับลูป) ซึ่งคุณไม่ทราบว่าคุณต้องการผลลัพธ์ทั้งหมดหรือไม่ต้องการจัดสรรหน่วยความจำสำหรับผลลัพธ์ทั้งหมดในเวลาเดียวกัน . หรือสำหรับสถานการณ์ที่ตัวกำเนิดใช้ตัวสร้างอื่นหรือใช้ทรัพยากรอื่น ๆ และจะสะดวกกว่าถ้าเกิดขึ้นช้าที่สุด
การใช้งานกับเครื่องกำเนิดไฟฟ้า (ที่เหมือนกันจริงๆ) ก็คือการโทรกลับด้วยการทำซ้ำ ในบางสถานการณ์คุณต้องการให้ฟังก์ชั่นทำงานมากมายและรายงานกลับไปที่ผู้โทรเป็นครั้งคราว ตามเนื้อผ้าคุณจะใช้ฟังก์ชันการเรียกกลับสำหรับสิ่งนี้ คุณส่งการเรียกกลับนี้ไปยังฟังก์ชั่นการทำงานและมันจะโทรติดต่อกลับเป็นระยะนี้ วิธีการกำเนิดคือการทำงานฟังก์ชั่น (ตอนนี้กำเนิด) ไม่รู้อะไรเกี่ยวกับการโทรกลับและเพียงแค่ให้ผลตอบแทนทุกครั้งที่มันต้องการรายงานบางสิ่งบางอย่าง ผู้เรียกแทนที่จะเขียนการโทรกลับแยกจากกันและส่งผ่านไปยังฟังก์ชันการทำงานการรายงานทั้งหมดจะทำงานใน 'for' วนรอบตัวกำเนิด
ตัวอย่างเช่นสมมติว่าคุณเขียนโปรแกรม 'ค้นหาระบบแฟ้ม' คุณสามารถทำการค้นหาอย่างครบถ้วนรวบรวมผลลัพธ์แล้วแสดงทีละรายการ ผลลัพธ์ทั้งหมดจะต้องถูกรวบรวมก่อนที่คุณจะแสดงผลลัพธ์แรกและผลลัพธ์ทั้งหมดจะอยู่ในหน่วยความจำในเวลาเดียวกัน หรือคุณสามารถแสดงผลลัพธ์ในขณะที่คุณค้นหาซึ่งจะมีหน่วยความจำมีประสิทธิภาพมากขึ้นและเป็นมิตรกับผู้ใช้มากขึ้น หลังสามารถทำได้โดยผ่านฟังก์ชั่นการพิมพ์ผลลัพธ์ไปยังฟังก์ชั่นการค้นหาระบบไฟล์หรือมันสามารถทำได้โดยเพียงแค่ทำให้ฟังก์ชั่นการค้นหาเป็นเครื่องกำเนิดไฟฟ้าและวนซ้ำผลที่ได้
หากคุณต้องการดูตัวอย่างของสองแนวทางหลังให้ดู os.path.walk () (ฟังก์ชั่นระบบไฟล์เก่าพร้อมการโทรกลับ) และ os.walk () (เครื่องมือสร้างระบบไฟล์เดินใหม่) แน่นอนถ้า คุณต้องการรวบรวมผลลัพธ์ทั้งหมดในรายการวิธีสร้างเป็นเรื่องเล็กน้อยที่จะแปลงเป็นรายการใหญ่:
big_list = list(the_generator)
yield
และjoin
หลังจากที่ได้รับผลลัพธ์ถัดไปจะไม่ทำงานแบบขนาน (และไม่มีตัวสร้างไลบรารี่มาตรฐานทำเช่นนี้; เครื่องกำเนิดหยุดที่แต่ละyield
จนกว่าจะมีการร้องขอค่าต่อไป หากเครื่องกำเนิดไฟฟ้ากำลังหุ้ม I / O ระบบปฏิบัติการอาจทำการแคชข้อมูลจากไฟล์โดยสมมติว่ามันจะถูกร้องขอในไม่ช้า แต่นั่นก็คือระบบปฏิบัติการ Python ไม่เกี่ยวข้อง
หนึ่งในเหตุผลในการใช้ตัวสร้างคือการทำให้โซลูชันชัดเจนสำหรับโซลูชันบางชนิด
อีกวิธีหนึ่งคือการจัดการผลลัพธ์ทีละครั้งโดยหลีกเลี่ยงการสร้างรายการผลลัพธ์ขนาดใหญ่ที่คุณจะดำเนินการแยกออกจากกัน
หากคุณมีฟังก์ชั่น fibonacci-up-to-n ดังนี้:
# function version
def fibon(n):
a = b = 1
result = []
for i in xrange(n):
result.append(a)
a, b = b, a + b
return result
คุณสามารถเขียนฟังก์ชันได้ง่ายขึ้นเช่นนี้:
# generator version
def fibon(n):
a = b = 1
for i in xrange(n):
yield a
a, b = b, a + b
ฟังก์ชั่นชัดเจนขึ้น และถ้าคุณใช้ฟังก์ชั่นเช่นนี้:
for x in fibon(1000000):
print x,
ในตัวอย่างนี้หากใช้รุ่นตัวสร้างรายการไอเท็มทั้งหมด 1000000 จะไม่ถูกสร้างขึ้นเลยเพียงค่าเดียวในแต่ละครั้ง กรณีนี้จะไม่เกิดขึ้นเมื่อใช้เวอร์ชันของรายการซึ่งจะสร้างรายการขึ้นก่อน
list(fibon(5))
ดูส่วน "แรงจูงใจ" ในPEP 255
การใช้เครื่องกำเนิดไฟฟ้าที่ไม่ชัดเจนกำลังสร้างฟังก์ชั่นอินเตอร์รัปต์ซึ่งช่วยให้คุณทำสิ่งต่าง ๆ เช่นอัปเดต UI หรือเรียกใช้งานหลายอย่าง "พร้อมกัน" (interleaved จริง ๆ ) ในขณะที่ไม่ได้ใช้เธรด
ฉันพบคำอธิบายที่ล้างข้อสงสัยของฉันนี้ เพราะมีความเป็นไปได้ที่คนที่ไม่รู้จักGenerators
ก็ไม่รู้เหมือนกันyield
กลับ
คำสั่ง return เป็นที่ที่ตัวแปรโลคัลทั้งหมดถูกทำลายและค่าผลลัพธ์จะถูกส่งคืน (ส่งคืน) แก่ผู้เรียก หากมีการเรียกใช้ฟังก์ชันเดียวกันในภายหลังฟังก์ชันจะได้รับชุดตัวแปรใหม่
ผล
แต่ถ้าตัวแปรท้องถิ่นไม่ถูกโยนทิ้งเมื่อเราออกจากฟังก์ชั่น นี่ก็หมายความว่าเราสามารถทำสิ่งresume the function
ที่เราทิ้งไว้ได้ นี่คือที่แนวคิดของการgenerators
แนะนำและyield
คำสั่งดำเนินการต่อที่function
ซ้ายออก
def generate_integers(N):
for i in xrange(N):
yield i
In [1]: gen = generate_integers(3)
In [2]: gen
<generator object at 0x8117f90>
In [3]: gen.next()
0
In [4]: gen.next()
1
In [5]: gen.next()
นั่นคือข้อแตกต่างระหว่างreturn
และyield
ข้อความใน Python
คำชี้แจงผลตอบแทนเป็นสิ่งที่ทำให้ฟังก์ชั่นเป็นเครื่องกำเนิดไฟฟ้า
ดังนั้นเครื่องกำเนิดไฟฟ้าจึงเป็นเครื่องมือที่ง่ายและมีประสิทธิภาพสำหรับการสร้างตัววนซ้ำ พวกเขาเขียนเหมือนฟังก์ชั่นปกติ แต่พวกเขาใช้yield
คำสั่งเมื่อใดก็ตามที่พวกเขาต้องการที่จะกลับข้อมูล แต่ละครั้งที่มีการเรียกใช้ถัดไป () ตัวสร้างจะทำงานต่อที่ที่ค้างอยู่ (มันจะจำค่าข้อมูลทั้งหมดและคำสั่งใดที่ถูกเรียกใช้งานครั้งล่าสุด)
สมมติว่าคุณมี 100 ล้านโดเมนในตาราง MySQL ของคุณและคุณต้องการอัปเดตอันดับ Alexa สำหรับแต่ละโดเมน
สิ่งแรกที่คุณต้องมีคือเลือกชื่อโดเมนจากฐานข้อมูล
สมมติว่าชื่อตารางของคุณและชื่อคอลัมน์domains
domain
ถ้าคุณใช้SELECT domain FROM domains
มันจะส่งคืน 100 ล้านแถวซึ่งจะใช้หน่วยความจำมาก ดังนั้นเซิร์ฟเวอร์ของคุณอาจมีปัญหา
ดังนั้นคุณตัดสินใจที่จะรันโปรแกรมแบบแบตช์ สมมุติว่าขนาดแบทช์ของเราคือ 1,000
ในชุดแรกของเราเราจะค้นหา 1,000 แถวแรกตรวจสอบอันดับ Alexa สำหรับแต่ละโดเมนและอัปเดตแถวฐานข้อมูล
ในชุดที่สองของเราเราจะทำงานใน 1,000 แถวถัดไป ในชุดที่สามของเรามันจะมาจาก 2001 ถึง 3000 และอื่น ๆ
ตอนนี้เราต้องการฟังก์ชั่นเครื่องกำเนิดไฟฟ้าที่สร้างแบทช์ของเรา
นี่คือฟังก์ชั่นเครื่องกำเนิดของเรา:
def ResultGenerator(cursor, batchsize=1000):
while True:
results = cursor.fetchmany(batchsize)
if not results:
break
for result in results:
yield result
อย่างที่คุณเห็นฟังก์ชั่นของเรายังคงให้yield
ผลลัพธ์ หากคุณใช้คำหลักreturn
แทนyield
ฟังก์ชั่นทั้งหมดจะสิ้นสุดลงเมื่อถึงการส่งคืน
return - returns only once
yield - returns multiple times
หากฟังก์ชั่นใช้คำหลักyield
แล้วมันเป็นเครื่องกำเนิดไฟฟ้า
ตอนนี้คุณสามารถทำซ้ำแบบนี้:
db = MySQLdb.connect(host="localhost", user="root", passwd="root", db="domains")
cursor = db.cursor()
cursor.execute("SELECT domain FROM domains")
for result in ResultGenerator(cursor):
doSomethingWith(result)
db.close()
บัฟเฟอร์ เมื่อมีประสิทธิภาพในการดึงข้อมูลในหน่วยขนาดใหญ่ แต่ประมวลผลเป็นชิ้นเล็ก ๆ จากนั้นตัวสร้างอาจช่วย:
def bufferedFetch():
while True:
buffer = getBigChunkOfData()
# insert some code to break on 'end of data'
for i in buffer:
yield i
ด้านบนช่วยให้คุณแยกบัฟเฟอร์จากการประมวลผลได้อย่างง่ายดาย ฟังก์ชั่นผู้ใช้งานสามารถรับค่าได้ทีละรายโดยไม่ต้องกังวลกับการบัฟเฟอร์
ฉันพบว่าเครื่องกำเนิดไฟฟ้ามีประโยชน์อย่างมากในการล้างรหัสของคุณและให้วิธีที่ไม่ซ้ำกันในการห่อหุ้มและทำให้เป็นรหัสโมดูลาร์ ในสถานการณ์ที่คุณต้องการสิ่งที่จะคายอย่างต่อเนื่องออกค่าอยู่บนพื้นฐานของการประมวลผลภายในของตัวเองและเมื่อว่าสิ่งที่ตอบสนองความต้องการที่จะเรียกว่าจากที่ใดก็ได้ในรหัสของคุณ (และไม่เพียง แต่ภายในห่วงหรือบล็อกเช่น) กำเนิดเป็นคุณสมบัติในการ ใช้.
ตัวอย่างนามธรรมจะเป็นตัวสร้างตัวเลขฟีโบนักชีที่ไม่ได้อยู่ภายในลูปและเมื่อมันถูกเรียกจากที่ใดก็ตามจะส่งคืนหมายเลขถัดไปตามลำดับเสมอ:
def fib():
first = 0
second = 1
yield first
yield second
while 1:
next = first + second
yield next
first = second
second = next
fibgen1 = fib()
fibgen2 = fib()
ตอนนี้คุณมีออบเจ็กต์ตัวสร้างหมายเลข Fibonacci สองตัวซึ่งคุณสามารถโทรจากที่ใดก็ได้ในรหัสของคุณ
>>> fibgen1.next(); fibgen1.next(); fibgen1.next(); fibgen1.next()
0
1
1
2
>>> fibgen2.next(); fibgen2.next()
0
1
>>> fibgen1.next(); fibgen1.next()
3
5
สิ่งที่น่ารักเกี่ยวกับเครื่องกำเนิดไฟฟ้าคือพวกเขาห่อหุ้มรัฐโดยไม่ต้องผ่านห่วงของการสร้างวัตถุ วิธีคิดอย่างหนึ่งเกี่ยวกับพวกเขาก็คือ "หน้าที่" ซึ่งจำสถานะภายในของพวกเขาได้
ฉันได้รับตัวอย่างฟีโบนักชีจากเครื่องสร้าง Python พวกมันคืออะไร? และด้วยจินตนาการเล็ก ๆ น้อย ๆ คุณสามารถเกิดขึ้นกับสถานการณ์อื่น ๆ อีกมากมายที่เครื่องกำเนิดไฟฟ้าสร้างทางเลือกที่ยอดเยี่ยมให้กับfor
ลูปและการทำซ้ำแบบดั้งเดิมอื่น ๆ
คำอธิบายง่ายๆ: พิจารณาfor
ข้อความ
for item in iterable:
do_stuff()
หลายครั้งที่รายการทั้งหมดในiterable
ไม่จำเป็นต้องมีตั้งแต่เริ่มต้น แต่สามารถสร้างได้ทันทีตามที่ต้องการ ทั้งสองนี้มีประสิทธิภาพมากขึ้น
ในบางครั้งคุณอาจไม่รู้รายการทั้งหมดล่วงหน้า ตัวอย่างเช่น:
for command in user_input():
do_stuff_with(command)
คุณไม่มีทางรู้คำสั่งทั้งหมดของผู้ใช้ล่วงหน้า แต่คุณสามารถใช้ลูปที่ดีเช่นนี้หากคุณมีเครื่องกำเนิดไฟฟ้าที่ส่งคำสั่งให้คุณ:
def user_input():
while True:
wait_for_command()
cmd = get_command()
yield cmd
ด้วยเครื่องกำเนิดไฟฟ้าคุณสามารถมีการวนซ้ำในลำดับที่ไม่มีที่สิ้นสุดซึ่งแน่นอนว่าเป็นไปไม่ได้เมื่อทำการวนซ้ำคอนเทนเนอร์
สิ่งที่ฉันโปรดปรานคือการใช้งาน "ตัวกรอง" และ "ลด"
สมมติว่าเรากำลังอ่านไฟล์และต้องการเพียงบรรทัดที่ขึ้นต้นด้วย "##"
def filter2sharps( aSequence ):
for l in aSequence:
if l.startswith("##"):
yield l
จากนั้นเราสามารถใช้ฟังก์ชั่นเครื่องกำเนิดไฟฟ้าในวงที่เหมาะสม
source= file( ... )
for line in filter2sharps( source.readlines() ):
print line
source.close()
ตัวอย่างการลดจะคล้ายกัน สมมติว่าเรามีไฟล์ที่เราต้องการค้นหาบล็อกของ<Location>...</Location>
บรรทัด [ไม่ใช่แท็ก HTML แต่บรรทัดที่มีลักษณะคล้ายแท็ก]
def reduceLocation( aSequence ):
keep= False
block= None
for line in aSequence:
if line.startswith("</Location"):
block.append( line )
yield block
block= None
keep= False
elif line.startsWith("<Location"):
block= [ line ]
keep= True
elif keep:
block.append( line )
else:
pass
if block is not None:
yield block # A partial block, icky
อีกครั้งเราสามารถใช้ตัวสร้างนี้ในลูปที่เหมาะสม
source = file( ... )
for b in reduceLocation( source.readlines() ):
print b
source.close()
แนวคิดก็คือฟังก์ชั่นเครื่องกำเนิดไฟฟ้าช่วยให้เราสามารถกรองหรือลดลำดับทำให้เกิดค่าหนึ่งค่าต่อครั้ง
fileobj.readlines()
จะอ่านไฟล์ทั้งหมดในรายการในหน่วยความจำเอาชนะวัตถุประสงค์ของการใช้เครื่องกำเนิดไฟฟ้า เนื่องจากวัตถุไฟล์นั้นสามารถทำซ้ำได้คุณจึงสามารถใช้for b in your_generator(fileobject):
แทน วิธีนี้ไฟล์ของคุณจะถูกอ่านทีละหนึ่งบรรทัดเพื่อหลีกเลี่ยงการอ่านไฟล์ทั้งหมด
ตัวอย่างที่ใช้งานได้จริงซึ่งคุณสามารถใช้เครื่องกำเนิดไฟฟ้าได้คือถ้าคุณมีรูปร่างบางชนิดและคุณต้องการย้ำมุมมุมขอบหรืออะไรก็ตาม สำหรับโครงการของฉัน (ซอร์สโค้ดที่นี่ ) ฉันมีสี่เหลี่ยมผืนผ้า:
class Rect():
def __init__(self, x, y, width, height):
self.l_top = (x, y)
self.r_top = (x+width, y)
self.r_bot = (x+width, y+height)
self.l_bot = (x, y+height)
def __iter__(self):
yield self.l_top
yield self.r_top
yield self.r_bot
yield self.l_bot
ตอนนี้ฉันสามารถสร้างรูปสี่เหลี่ยมผืนผ้าและวนรอบมุมของมันได้:
myrect=Rect(50, 50, 100, 100)
for corner in myrect:
print(corner)
แทนที่จะ__iter__
คุณอาจจะมีวิธีการที่เรียกว่าiter_corners
for corner in myrect.iter_corners()
มันเป็นเพียงที่สง่างามมากขึ้นเพื่อใช้__iter__
ตั้งแต่นั้นเราสามารถใช้ชื่ออินสแตนซ์ชั้นโดยตรงในfor
การแสดงออก
บางคำตอบที่ดีที่นี่ แต่ฉันขอแนะนำให้อ่านแบบฝึกหัด Python Functional Programmingอย่างสมบูรณ์ซึ่งจะช่วยอธิบายกรณีการใช้เครื่องกำเนิดไฟฟ้าที่มีประสิทธิภาพมากขึ้น
เนื่องจากวิธีการส่งของตัวสร้างไม่ได้ถูกกล่าวถึงนี่คือตัวอย่าง:
def test():
for i in xrange(5):
val = yield
print(val)
t = test()
# Proceed to 'yield' statement
next(t)
# Send value to yield
t.send(1)
t.send('2')
t.send([3])
มันแสดงให้เห็นความเป็นไปได้ในการส่งค่าไปยังเครื่องกำเนิดไฟฟ้าที่ทำงานอยู่ หลักสูตรขั้นสูงเพิ่มเติมเกี่ยวกับเครื่องกำเนิดไฟฟ้าในวิดีโอด้านล่าง (รวมถึงyield
สำรวจเครื่องปั่นไฟสำหรับการประมวลผลแบบขนานการหลีกเลี่ยงการ จำกัด การเรียกซ้ำ ฯลฯ )
ฉันใช้เครื่องกำเนิดไฟฟ้าเมื่อเว็บเซิร์ฟเวอร์ของเราทำหน้าที่เป็นพร็อกซี:
กองสิ่งของ เมื่อใดก็ตามที่คุณต้องการสร้างลำดับของรายการ แต่ไม่ต้องการที่จะ 'ทำให้เป็นจริง' พวกเขาทั้งหมดลงในรายการในครั้งเดียว ตัวอย่างเช่นคุณอาจมีเครื่องมือสร้างแบบง่ายที่ส่งกลับตัวเลขที่สำคัญ:
def primes():
primes_found = set()
primes_found.add(2)
yield 2
for i in itertools.count(1):
candidate = i * 2 + 1
if not all(candidate % prime for prime in primes_found):
primes_found.add(candidate)
yield candidate
จากนั้นคุณสามารถใช้สิ่งนั้นเพื่อสร้างผลิตภัณฑ์ในช่วงเวลาต่อมา:
def prime_products():
primeiter = primes()
prev = primeiter.next()
for prime in primeiter:
yield prime * prev
prev = prime
นี่เป็นตัวอย่างที่ค่อนข้างง่าย แต่คุณสามารถดูว่ามันจะมีประโยชน์สำหรับการประมวลผลชุดข้อมูลขนาดใหญ่ (อาจไม่มีที่สิ้นสุด!) โดยไม่ต้องสร้างชุดข้อมูลล่วงหน้าซึ่งเป็นหนึ่งในการใช้งานที่ชัดเจนมากขึ้น
ยังดีสำหรับการพิมพ์หมายเลขเฉพาะสูงสุด n:
def genprime(n=10):
for num in range(3, n+1):
for factor in range(2, num):
if num%factor == 0:
break
else:
yield(num)
for prime_num in genprime(100):
print(prime_num)