เพื่อให้เข้าใจถึงสิ่งที่yield
ไม่คุณต้องเข้าใจสิ่งที่กำเนิดเป็น และก่อนที่คุณสามารถเข้าใจกำเนิดคุณต้องเข้าใจiterables
Iterables
เมื่อคุณสร้างรายการคุณสามารถอ่านรายการได้ทีละรายการ การอ่านรายการหนึ่งต่อหนึ่งเรียกว่าการวนซ้ำ:
>>> mylist = [1, 2, 3]
>>> for i in mylist:
... print(i)
1
2
3
mylist
เป็นซ้ำได้ เมื่อคุณใช้ความเข้าใจในรายการคุณจะสร้างรายการและทำซ้ำได้:
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
... print(i)
0
1
4
ทุกสิ่งที่คุณสามารถใช้ได้คือ " for... in...
" เปิดใช้ซ้ำ lists
,, strings
ไฟล์ ...
Iterables เหล่านี้มีประโยชน์เพราะคุณสามารถอ่านได้มากเท่าที่คุณต้องการ แต่คุณเก็บค่าทั้งหมดไว้ในหน่วยความจำและนี่อาจไม่ใช่สิ่งที่คุณต้องการเสมอเมื่อคุณมีค่าจำนวนมาก
เครื่องปั่นไฟ
เครื่องกำเนิดเป็นตัววนซ้ำคุณสามารถวนซ้ำได้หนึ่งครั้งเท่านั้น เครื่องกำเนิดไฟฟ้าจะไม่เก็บค่าทั้งหมดไว้ในหน่วยความจำ แต่จะสร้างค่าได้ทันที :
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
... print(i)
0
1
4
มันเป็นเพียงเหมือนเดิมยกเว้นที่คุณใช้แทน()
[]
แต่คุณไม่สามารถดำเนินการfor i in mygenerator
เป็นครั้งที่สองได้เนื่องจากเครื่องกำเนิดไฟฟ้าสามารถใช้งานได้เพียงครั้งเดียว: พวกเขาคำนวณ 0 แล้วลืมเกี่ยวกับมันและคำนวณ 1 และสิ้นสุดการคำนวณ 4 ทีละคน
ผล
yield
เป็นคำหลักที่ใช้เช่นreturn
ยกเว้นฟังก์ชันจะส่งคืนตัวกำเนิด
>>> def createGenerator():
... mylist = range(3)
... for i in mylist:
... yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
... print(i)
0
1
4
นี่เป็นตัวอย่างที่ไร้ประโยชน์ แต่มันมีประโยชน์เมื่อคุณรู้ว่าฟังก์ชั่นของคุณจะคืนค่าจำนวนมากที่คุณจะต้องอ่านเพียงครั้งเดียว
หากต้องการหลักyield
คุณต้องเข้าใจว่าเมื่อคุณเรียกใช้ฟังก์ชันรหัสที่คุณเขียนในส่วนของฟังก์ชันจะไม่ทำงาน ฟังก์ชั่นส่งกลับเฉพาะวัตถุเครื่องกำเนิดไฟฟ้าซึ่งเป็นเรื่องยุ่งยากเล็กน้อย :-)
จากนั้นรหัสของคุณจะดำเนินการต่อจากจุดที่ค้างไว้ในแต่ละครั้งที่for
ใช้ตัวสร้าง
ตอนนี้ส่วนที่ยาก:
ครั้งแรกที่for
เรียกวัตถุตัวกำเนิดที่สร้างจากฟังก์ชั่นของคุณมันจะเรียกใช้รหัสในฟังก์ชั่นของคุณตั้งแต่เริ่มต้นจนกระทั่งมันกระทบyield
แล้วมันจะคืนค่าแรกของลูป จากนั้นแต่ละการเรียกที่ตามมาจะรันการวนซ้ำอีกครั้งของลูปที่คุณเขียนในฟังก์ชันและส่งคืนค่าถัดไป yield
นี้จะดำเนินต่อไปจนถึงเครื่องกำเนิดไฟฟ้าถือว่าเป็นที่ว่างเปล่าซึ่งเกิดขึ้นเมื่อฟังก์ชั่นทำงานได้โดยไม่ต้องกดปุ่ม "if/else"
ที่สามารถจะเป็นเพราะวงได้มาถึงจุดสิ้นสุดหรือเพราะคุณไม่ได้ความพึงพอใจ
รหัสของคุณอธิบาย
เครื่องกำเนิดไฟฟ้า:
# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):
# Here is the code that will be called each time you use the generator object:
# If there is still a child of the node object on its left
# AND if the distance is ok, return the next child
if self._leftchild and distance - max_dist < self._median:
yield self._leftchild
# If there is still a child of the node object on its right
# AND if the distance is ok, return the next child
if self._rightchild and distance + max_dist >= self._median:
yield self._rightchild
# If the function arrives here, the generator will be considered empty
# there is no more than two values: the left and the right children
Caller:
# Create an empty list and a list with the current object reference
result, candidates = list(), [self]
# Loop on candidates (they contain only one element at the beginning)
while candidates:
# Get the last candidate and remove it from the list
node = candidates.pop()
# Get the distance between obj and the candidate
distance = node._get_dist(obj)
# If distance is ok, then you can fill the result
if distance <= max_dist and distance >= min_dist:
result.extend(node._values)
# Add the children of the candidate in the candidate's list
# so the loop will keep running until it will have looked
# at all the children of the children of the children, etc. of the candidate
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result
รหัสนี้มีส่วนประกอบอัจฉริยะหลายส่วน:
วนซ้ำในรายการ แต่รายการจะขยายในขณะที่วนซ้ำจะถูกทำซ้ำ :-) มันเป็นวิธีที่กระชับในการอ่านข้อมูลที่ซ้อนกันเหล่านี้แม้ว่ามันจะค่อนข้างอันตรายเพราะคุณสามารถจบด้วยวงวนไม่สิ้นสุด ในกรณีนี้ให้candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
หมดค่าทั้งหมดของตัวสร้าง แต่while
สร้างวัตถุตัวสร้างใหม่ซึ่งจะสร้างค่าที่แตกต่างจากค่าก่อนหน้าเนื่องจากไม่ได้ใช้กับโหนดเดียวกัน
extend()
วิธีเป็นวิธีที่รายการวัตถุที่คาดว่า iterable และเพิ่มคุณค่าในรายการ
เรามักจะส่งรายการไปที่:
>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]
แต่ในรหัสของคุณจะได้รับตัวสร้างซึ่งดีเพราะ:
- คุณไม่จำเป็นต้องอ่านค่าสองครั้ง
- คุณอาจมีลูกจำนวนมากและคุณไม่ต้องการให้เก็บไว้ในความทรงจำ
และใช้งานได้เพราะ Python ไม่สนใจว่าอาร์กิวเมนต์ของเมธอดนั้นเป็นรายการหรือไม่ Python คาดว่าจะใช้งานได้ดังนั้นมันจะทำงานกับสตริงรายการ tuples และเครื่องกำเนิดไฟฟ้า! นี่เรียกว่าการพิมพ์เป็ดและเป็นหนึ่งในสาเหตุที่ Python เจ๋งมาก แต่นี่เป็นอีกเรื่องหนึ่งสำหรับคำถามอื่น ...
คุณสามารถหยุดที่นี่หรืออ่านนิดหน่อยเพื่อดูการใช้เครื่องกำเนิดไฟฟ้าขั้นสูง:
การควบคุมความเหนื่อยล้าของเครื่องกำเนิดไฟฟ้า
>>> class Bank(): # Let's create a bank, building ATMs
... crisis = False
... def create_atm(self):
... while not self.crisis:
... yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
... print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...
หมายเหตุ:สำหรับ Python 3 ให้ใช้print(corner_street_atm.__next__())
หรือprint(next(corner_street_atm))
มันจะมีประโยชน์สำหรับสิ่งต่าง ๆ เช่นการควบคุมการเข้าถึงทรัพยากร
Itertools เพื่อนที่ดีที่สุดของคุณ
โมดูล itertools มีฟังก์ชั่นพิเศษเพื่อจัดการ iterables เคยต้องการที่จะทำสำเนากำเนิด? เชนสองกำเนิด? ค่ากลุ่มในรายการซ้อนกับหนึ่งซับ? Map / Zip
โดยไม่ต้องสร้างรายการอื่น?
import itertools
แล้วก็
ตัวอย่าง? ลองดูคำสั่งที่เป็นไปได้สำหรับการแข่งม้าสี่ตัว
>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
(1, 2, 4, 3),
(1, 3, 2, 4),
(1, 3, 4, 2),
(1, 4, 2, 3),
(1, 4, 3, 2),
(2, 1, 3, 4),
(2, 1, 4, 3),
(2, 3, 1, 4),
(2, 3, 4, 1),
(2, 4, 1, 3),
(2, 4, 3, 1),
(3, 1, 2, 4),
(3, 1, 4, 2),
(3, 2, 1, 4),
(3, 2, 4, 1),
(3, 4, 1, 2),
(3, 4, 2, 1),
(4, 1, 2, 3),
(4, 1, 3, 2),
(4, 2, 1, 3),
(4, 2, 3, 1),
(4, 3, 1, 2),
(4, 3, 2, 1)]
ทำความเข้าใจกลไกภายในของการทำซ้ำ
การวนซ้ำเป็นกระบวนการที่อ้างถึง iterables (การนำ__iter__()
เมธอดไปใช้) และตัววนซ้ำ (การใช้__next__()
เมธอด) Iterables เป็นวัตถุใด ๆ ที่คุณสามารถรับตัววนซ้ำได้ การวนซ้ำเป็นวัตถุที่ให้คุณทำซ้ำบน iterables
มีมากขึ้นเกี่ยวกับเรื่องนี้ในบทความนี้เกี่ยวกับวิธีการfor
วนรอบการทำงาน
yield
ไม่ได้วิเศษเท่าคำตอบนี้แสดงให้เห็น เมื่อคุณเรียกใช้ฟังก์ชันที่มีyield
คำสั่งใดก็ได้คุณจะได้รับวัตถุตัวสร้าง แต่ไม่มีโค้ดที่ทำงาน จากนั้นทุกครั้งที่คุณดึงวัตถุออกจากเครื่องกำเนิด Python จะเรียกใช้รหัสในฟังก์ชันจนกว่าจะมีyield
คำสั่งจากนั้นหยุดและส่งวัตถุ เมื่อคุณแยกวัตถุอื่นออก Python จะดำเนินการต่อหลังจากนั้นyield
และดำเนินต่อไปจนกว่าจะถึงวัตถุอื่นyield
(มักจะเป็นวัตถุเดียวกัน แต่ทำซ้ำในภายหลัง) สิ่งนี้จะดำเนินต่อไปจนกระทั่งฟังก์ชั่นจะสิ้นสุดลง ณ จุดที่เครื่องกำเนิดไฟฟ้านั้นถือว่าหมด