มีสิ่งอำนวยความสะดวกภาษากำเนิดเช่นyield
ความคิดที่ดี?
ฉันต้องการที่จะตอบคำถามนี้จากมุมมองของงูหลามกับหนักแน่นใช่มันเป็นความคิดที่ดี
ฉันจะเริ่มต้นด้วยการตอบคำถามและสมมติฐานในคำถามของคุณก่อนจากนั้นแสดงให้เห็นถึงความแพร่หลายของเครื่องกำเนิดไฟฟ้าและประโยชน์ที่ไม่สมเหตุสมผลใน Python ในภายหลัง
ด้วยฟังก์ชั่นที่ไม่ใช่เครื่องกำเนิดไฟฟ้าปกติคุณสามารถเรียกมันได้และถ้ามันได้รับอินพุตเดียวกันมันก็จะส่งคืนเอาต์พุตเดียวกัน ด้วยผลผลิตจะส่งคืนเอาต์พุตที่ต่างกันตามสถานะภายใน
นี่เป็นเท็จ วิธีการเกี่ยวกับวัตถุสามารถคิดเป็นหน้าที่ตัวเองด้วยสถานะภายในของตนเอง ใน Python เนื่องจากทุกอย่างเป็นวัตถุคุณสามารถได้รับวิธีการจากวัตถุและส่งผ่านวิธีการนั้น (ซึ่งถูกผูกไว้กับวัตถุที่มาจากดังนั้นจึงจดจำสถานะของมัน)
ตัวอย่างอื่น ๆ รวมถึงฟังก์ชั่นการสุ่มอย่างจงใจรวมถึงวิธีการป้อนข้อมูลเช่นเครือข่ายระบบไฟล์และเทอร์มินัล
ฟังก์ชั่นเช่นนี้เหมาะสมกับกระบวนทัศน์ทางภาษาอย่างไร
หากกระบวนทัศน์ทางภาษารองรับสิ่งต่าง ๆ เช่นฟังก์ชั่นชั้นหนึ่งและเครื่องกำเนิดไฟฟ้ารองรับคุณสมบัติภาษาอื่น ๆ เช่นโปรโตคอล Iterable แล้วพวกเขาก็เข้ากันได้อย่างลงตัว
จริง ๆ แล้วมันเป็นการทำลายอนุสัญญาใด ๆ
ไม่เลยเพราะมันถูกทำให้เป็นภาษาการประชุมถูกสร้างขึ้นและรวมถึง (หรือต้องการ!) การใช้เครื่องกำเนิดไฟฟ้า
การคอมไพล์เลอร์ / ล่ามภาษาโปรแกรมต้องแยกออกจากอนุสัญญาใด ๆ เพื่อใช้คุณลักษณะดังกล่าว
เช่นเดียวกับคุณสมบัติอื่น ๆ คอมไพเลอร์ก็ต้องได้รับการออกแบบเพื่อรองรับคุณสมบัติ ในกรณีของ Python ฟังก์ชั่นเป็นวัตถุที่มีสถานะอยู่แล้ว (เช่นอาร์กิวเมนต์เริ่มต้นและหมายเหตุประกอบฟังก์ชัน)
ภาษาต้องใช้หลายเธรดเพื่อให้คุณสมบัตินี้ทำงานหรือสามารถทำได้โดยไม่ต้องใช้เธรดเทคโนโลยี?
ข้อเท็จจริงที่น่าสนุก: การใช้ Python เริ่มต้นไม่สนับสนุนการทำเกลียวเลย มันมีล็อค Interpreter ทั่วโลก (GIL) ดังนั้นจึงไม่มีสิ่งใดทำงานพร้อมกันเว้นแต่ว่าคุณได้หมุนกระบวนการที่สองเพื่อเรียกใช้อินสแตนซ์อื่นของ Python
หมายเหตุ: ตัวอย่างอยู่ใน Python 3
เกินกว่าอัตราผลตอบแทน
ในขณะที่yield
คำสำคัญสามารถใช้ในฟังก์ชั่นใด ๆ ที่จะทำให้มันกลายเป็นเครื่องกำเนิดไฟฟ้ามันไม่ได้เป็นวิธีเดียวที่จะทำให้มัน Python มีเครื่องมือสร้างนิพจน์ซึ่งเป็นวิธีที่มีประสิทธิภาพในการแสดงเครื่องกำเนิดไฟฟ้าอย่างชัดเจนในแง่ของการทำซ้ำอื่น (รวมถึงเครื่องกำเนิดไฟฟ้าอื่น ๆ )
>>> pairs = ((x,y) for x in range(10) for y in range(10) if y >= x)
>>> pairs
<generator object <genexpr> at 0x0311DC90>
>>> sum(x*y for x,y in pairs)
1155
อย่างที่คุณเห็นไม่เพียง แต่เป็นไวยากรณ์ที่สะอาดและอ่านได้ แต่มีฟังก์ชันในตัวเช่นsum
accept generators
กับ
ตรวจสอบข้อเสนอการเพิ่มประสิทธิภาพของงูหลามสำหรับคำสั่งด้วย มันแตกต่างจากที่คุณคาดหวังจากคำสั่ง With ในภาษาอื่น ๆ ด้วยความช่วยเหลือเล็กน้อยจากห้องสมุดมาตรฐานผู้สร้าง Python ทำงานอย่างสวยงามในฐานะผู้จัดการบริบทสำหรับพวกเขา
>>> from contextlib import contextmanager
>>> @contextmanager
def debugWith(arg):
print("preprocessing", arg)
yield arg
print("postprocessing", arg)
>>> with debugWith("foobar") as s:
print(s[::-1])
preprocessing foobar
raboof
postprocessing foobar
แน่นอนว่าการพิมพ์สิ่งต่าง ๆ เป็นเรื่องที่น่าเบื่อที่สุดที่คุณสามารถทำได้ที่นี่ แต่มันแสดงผลลัพธ์ที่มองเห็นได้ ตัวเลือกที่น่าสนใจ ได้แก่ การจัดการทรัพยากรอัตโนมัติ (การเปิดและปิดไฟล์ / สตรีม / การเชื่อมต่อเครือข่าย), ล็อคการทำงานพร้อมกัน, การปิดหรือแทนที่ฟังก์ชั่นชั่วคราว, และคลายการบีบอัดข้อมูล ถ้าฟังก์ชั่นการโทรนั้นเหมือนการฉีดโค้ดเข้าไปในโค้ดของคุณดังนั้นข้อความสั่งจะเหมือนกับการตัดส่วนของโค้ดของคุณในโค้ดอื่น อย่างไรก็ตามคุณใช้มันเป็นตัวอย่างที่ชัดเจนของการเชื่อมต่อที่ง่ายเข้ากับโครงสร้างภาษา เครื่องมือสร้างผลตอบแทนไม่ใช่วิธีเดียวในการสร้างผู้จัดการบริบท แต่แน่นอนว่าเป็นวิธีที่สะดวก
สำหรับและอ่อนเพลียบางส่วน
สำหรับลูปใน Python ทำงานในวิธีที่น่าสนใจ พวกเขามีรูปแบบต่อไปนี้:
for <name> in <iterable>:
...
ก่อนอื่นนิพจน์ที่ฉันเรียก<iterable>
นั้นจะถูกประเมินเพื่อให้ได้วัตถุที่ทำซ้ำได้ ประการที่สอง iterable __iter__
เรียกใช้และ iterator ที่ได้จะถูกจัดเก็บไว้เบื้องหลัง Subsequenty, __next__
ถูกเรียกบน iterator <name>
ที่จะได้รับค่าเชื่อมโยงกับชื่อที่คุณใส่ใน ขั้นตอนนี้ซ้ำจนกว่าการเรียกร้องให้พ่น__next__
StopIteration
ข้อยกเว้นถูกกลืนโดย for for loop และการดำเนินการต่อจากตรงนั้น
กลับมาหาเครื่องปั่นไฟ: เมื่อคุณโทรหา__iter__
เครื่องกำเนิดไฟฟ้ามันจะกลับมาเอง
>>> x = (a for a in "boring generator")
>>> id(x)
51502272
>>> id(x.__iter__())
51502272
สิ่งนี้หมายความว่าคุณสามารถแยกการวนซ้ำบางอย่างจากสิ่งที่คุณต้องการจะทำกับมันและเปลี่ยนพฤติกรรมนั้นผ่านไปมา ด้านล่างโปรดสังเกตว่าตัวสร้างเดียวกันนี้ใช้งานอย่างไรในสองลูปและในวินาทีที่มันเริ่มทำงานจากที่ที่มันออกจากครั้งแรก
>>> generator = (x for x in 'more boring stuff')
>>> for letter in generator:
print(ord(letter))
if letter > 'p':
break
109
111
114
>>> for letter in generator:
print(letter)
e
b
o
r
i
n
g
s
t
u
f
f
การประเมินผลขี้เกียจ
ข้อเสียอย่างหนึ่งของเครื่องกำเนิดไฟฟ้าเมื่อเทียบกับรายการคือสิ่งเดียวที่คุณสามารถเข้าถึงได้ในเครื่องกำเนิดไฟฟ้าคือสิ่งถัดไปที่ออกมา คุณไม่สามารถย้อนกลับและเป็นผลลัพธ์ก่อนหน้าหรือข้ามไปยังผลลัพธ์ในภายหลังโดยไม่ต้องผ่านผลลัพธ์ระดับกลาง ด้านขึ้นของเครื่องกำเนิดไฟฟ้านี้สามารถใช้หน่วยความจำเกือบจะไม่เมื่อเทียบกับรายการเทียบเท่า
>>> import sys
>>> sys.getsizeof([x for x in range(10000)])
43816
>>> sys.getsizeof(range(10000000000))
24
>>> sys.getsizeof([x for x in range(10000000000)])
Traceback (most recent call last):
File "<pyshell#10>", line 1, in <module>
sys.getsizeof([x for x in range(10000000000)])
File "<pyshell#10>", line 1, in <listcomp>
sys.getsizeof([x for x in range(10000000000)])
MemoryError
เครื่องกำเนิดไฟฟ้าสามารถถูกล่ามโซ่อย่างเกียจคร้าน
logfile = open("logs.txt")
lastcolumn = (line.split()[-1] for line in logfile)
numericcolumn = (float(x) for x in lastcolumn)
print(sum(numericcolumn))
บรรทัดแรกที่สองและที่สามกำหนดตัวกำเนิดแต่ละตัว แต่ไม่ได้ทำงานจริง เมื่อเรียกบรรทัดสุดท้าย sum จะถามตัวเลขสำหรับค่า numericcolumn ต้องการค่าจาก lastcolumn และ lastcolumn จะขอค่าจาก logfile ซึ่งจริงๆแล้วจะอ่านบรรทัดจากไฟล์ สแต็กนี้จะผ่อนคลายจนกว่าผลรวมจะได้รับจำนวนเต็มแรก จากนั้นกระบวนการจะเกิดขึ้นอีกครั้งสำหรับบรรทัดที่สอง ณ จุดนี้ผลรวมมีจำนวนเต็มสองจำนวนและรวมเข้าด้วยกัน โปรดทราบว่าบรรทัดที่สามยังไม่ได้อ่านจากไฟล์ จากนั้น Sum จะดำเนินการขอค่าจาก numericcolumn (ลบเลือนโดยสิ้นเชิงไปยังส่วนอื่น ๆ ของ chain) และเพิ่มเข้าไปจนกระทั่งตัวเลขตัวเลขนั้นหมดไป
ส่วนที่น่าสนใจจริงๆที่นี่คือการอ่านบรรทัดบริโภคและทิ้งเป็นรายบุคคล ไม่มีจุดใดไฟล์ทั้งหมดในหน่วยความจำทั้งหมดในครั้งเดียว จะเกิดอะไรขึ้นถ้าไฟล์บันทึกนี้คือเทราไบต์ ใช้งานได้เพราะอ่านครั้งละหนึ่งบรรทัดเท่านั้น
ข้อสรุป
นี่ไม่ใช่การตรวจสอบที่สมบูรณ์ของการใช้งานของเครื่องกำเนิดไฟฟ้าทั้งหมดใน Python โดยเฉพาะอย่างยิ่งฉันข้ามเครื่องกำเนิดไฟฟ้าไม่ จำกัด เครื่องสถานะการส่งผ่านค่ากลับและความสัมพันธ์ของพวกเขากับ coroutines
ฉันเชื่อว่ามันเพียงพอที่จะแสดงให้เห็นว่าคุณสามารถมีกำเนิดเป็นคุณสมบัติทางภาษาที่มีประโยชน์และรวมเข้าด้วยกัน
yield
เป็นหลักเครื่องยนต์รัฐ ไม่ได้หมายถึงการส่งคืนผลลัพธ์เดียวกันทุกครั้ง สิ่งที่จะทำอย่างไรกับความแน่นอนแน่นอนคือคืนค่ารายการถัดไปในจำนวนที่นับได้ทุกครั้งที่เรียกใช้ ไม่จำเป็นต้องมีเธรด คุณต้องปิด (มากหรือน้อย) เพื่อรักษาสถานะปัจจุบัน