reversed ทำให้เกิดสิ่งที่เรียกว่าอิเทอเรเตอร์ และในบทที่ ๒๑ พูดถึงฟังก์ชัน map และ filter ซึ่งก็ทำให้เกิดอิเทอเรเตอร์เช่นกัน แต่ไม่ได้อธิบายขยายความว่าอิเทอเรเตอร์คืออะไร ดังนั้นจึงจะมาพูดถึงในบทนี้for ซึ่งต้องใช้งานเป็นประจำ แม้ว่าโดยทั่วไปผู้เรียนเริ่มต้นอาจยังไม่มีความจำเป็นต้องเข้าใจว่า for เป็นการทำงานของอิเทอเรเตอร์ก็ตามfor เพื่อวนซ้ำlis = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30]
for i in lis:
print(i)
for นั้นก็ไม่ใช่ว่าจำเป็นจะต้องวนจนครบทุกรอบเสมอไป บางครั้งอาจหลุดออกมาก่อน เช่นเจอคำสั่ง break กลางคันเมื่อเข้าเงื่อนไขอะไรบางอย่างdef เหมือนกับฟังก์ชันธรรมดาทั่วไปreturn เพื่อส่งค่าคืนกลับ แต่หากต้องการสร้างเจเนอเรเตอร์ขึ้นจะใช้คำสั่ง yield แทน returnyield อยู่ในฟังก์ชัน ฟังก์ชันนั้นก็จะกลายเป็นฟังก์ชันสร้างเจเนอเรเตอร์ทันที และคุณสมบัติการทำงานจะต่างไปจากฟังก์ชันทั่วไปทันทีreturn ที่ไหนฟังก์ชันก็จะสิ้นสุดการทำงานทันทีพร้อมทั้งส่งค่าคืนกลับมา จากนั้นเมื่อฟังก์ชันนี้ถูกเรียกซ้ำก็จะเริ่มทำงานใหม่ตั้งแต่ต้น ไม่ได้มีอะไรเกี่ยวข้องกับรอบที่แล้วyield นั้นการทำงานจะต่างออกไป คือเมื่อถูกเรียกใช้งานมันจะยังไม่ได้เริ่มทำงาน แต่จะแค่เริ่มทำการสร้างเจเนอเรเตอร์ขึ้นมาfor in โดยเจเนอเรเตอร์นี้จะถูกใช้แทนข้อมูลชนิดกลุ่มyield และส่งค่าคืนกลับให้กับตัวแปรที่มารับค่าในคำสั่ง foryield ตัวนั้น และต่อไปสุดที่ yield ตัวต่อไป แล้วก็ส่งค่าคืนกลับมาสำหรับใช้ในรอบต่อไปyield อีกต่อไปแล้วฟังก์ชันนั้นจึงหยุดทำงาน และวังวน for ก็จะสิ้นสุดลงเท่านี้def gensoukyou():
yield 'gen' # ส่งค่าคืนกลับครั้งที่ ๑
yield 'sou' # ส่งค่าคืนกลับครั้งที่ ๒
yield 'kyou' # ส่งค่าคืนกลับครั้งที่ ๓
gensou = gensoukyou() # สร้างเจเนอเรเตอร์ขึ้นจากฟังก์ชัน
for g in gensou: # ใช้เจเนอเรเตอร์แทนตัวแปรชนิดกลุ่มในคำสั่ง for
print(g)
gen
sou
kyou
gensoukyou สร้างเจเนอเรเตอร์ชื่อ gensou จากนั้น gensou ก็ถูกนำไปใช้ในคำสั่ง for โดยคืนค่าที่อยู่ข้างหลัง yield มาทีละค่าตามลำดับgensou เพื่อมาเก็บเจเนอเรเตอร์นั้นอาจไม่จำเป็นก็ได้ อาจเขียนเป็น
for g in gensoukyou():
print(g)
for เราอาจประกาศสร้างลิสต์ขึ้นมาก่อนแล้วค่อยเอาตัวแปรที่เก็บลิสต์นั้นมาใช้ หรือจะสร้างลิสต์ขึ้นตรงนั้นเพื่อใช้ทันทีเลยก็ได้type ดูได้
print(type(gensoukyou)) # ได้ <class 'function'>
print(type(gensoukyou())) # ได้ <class 'generator'>
gensou = gensoukyou()
print(type(gensou)) # ได้ <class 'generator'>
def genjitsu(a,b,c):
yield a
yield b
yield c
for g in genjitsu('dai','su','ki'):
print(g)
dai
su
ki
yield หลายๆตัวแบบนี้ การใช้ yield มักจะใช้คู่กับการวนซ้ำด้วย while หรือ for มากกว่า เช่น
def genkotsu(n):
i = 0
while(i<n):
i += 1
yield i
for g in genkotsu(10):
print(g, end=' ')
1 2 3 4 5 6 7 8 9 10
while ตามจำนวนครั้งที่ป้อนเข้าไป ในที่นี้คือ 10 โดยในแต่ละรอบจะเจอ yield และคืนค่ากลับมาทุกครั้งและ i ก็มากขึ้นเรื่อยๆ จนเมื่อ i เท่ากับ 10 ก็จะออกจากวังวนไม่เจอ yield อีก การทำงานของฟังก์ชันจึงสิ้นสุดลงi ซึ่งประกาศขึ้นภายในฟังก์ชันนี้มีการเปลี่ยนแปลงค่าไปเรื่อยๆทุกครั้ง ไม่ได้กลับมาเริ่มต้นใหม่ทุกครั้งที่ฟังก์ชันหยุดทำงานชั่วคราวหลังเจอ yield ซึ่งต่างจากฟังก์ชันทั่วไปที่ใช้ return ซึ่งเมื่อสิ้นสุดการทำงานตัวแปรจะต้องถูกลบทิ้งไปreversed คือกลับลำดับของข้อมูลกลุ่ม
def genreversed(c):
i = len(c)-1
while(i>=0):
yield c[i]
i -= 1
print([x for x in genreversed(range(3,9))]) # ได้ [8, 7, 6, 5, 4, 3]
def genfac(n):
i = 2
fac = 1
while(i<=n+1):
yield fac
fac *= i
i += 1
for g in genfac(7):
print(g, end='~')
1~2~6~24~120~720~5040~
def genfib(n):
i = 1
a = 0
b = 1
while(i<=n):
yield b
a,b = b,a+b
i += 1
for g in genfib(7):
print(g, end='::')
1::1::2::3::5::8::13::
return สามารถลองไปดูเพื่อเปรียบเทียบกันได้def fib(x):
i = 2
a = 1
b = 1
while(i<x):
a,b = b,a+b
i += 1
return b
for i in range(1,8):
print(fib(i), end='::')
for แล้ว บางอย่างที่ออบเจ็กต์ชนิดกลุ่มทำได้นั้นอิเทอเรเตอร์ก็สามารถทำได้เช่นกันfor
print([x*10 for x in genfib(7)]) # ได้ [10, 10, 20, 30, 50, 80, 130]
print(list(genfib(7))) # ได้ [1, 1, 2, 3, 5, 8, 13]
print(tuple(genfib(7))) # ได้ (1, 1, 2, 3, 5, 8, 13)
print(set(genfib(7))) # ได้ {1, 2, 3, 5, 8, 13}
any, all, map และ filter ก็ยังใช้กับอิเทอเรเตอร์ได้
print(list(map(str,genfib(7)))) # ได้ ['1', '1', '2', '3', '5', '8', '13']
print(list(filter(lambda x:x%2,genfib(7)))) # ได้ [1, 1, 3, 5, 13]
print(all(genfib(7))) # ได้ True
in เพื่อตรวจสอบว่ามีออบเจ็กต์ที่ต้องการอยู่ในกลุ่มหรือเปล่าได้ด้วย
print(13 in genfib(7)) # ได้ True
print(9 in genfib(9)) # ได้ False
.join ได้ด้วย เช่น
def genjimonogatari():
yield 'เกน'
yield 'จิ'
yield 'โม'
yield 'โน'
yield 'งา'
yield 'ตา'
yield 'ริ'
print('-'.join(genjimonogatari())) # ได้ เกน-จิ-โม-โน-งา-ตา-ริ
for นั้น แท้จริงแล้วมีกลไกที่อยู่เบื้องหลัง นั่นคือความจริงแล้วคำสั่ง for เป็นการสั่งให้เจเนอเรเตอร์ใช้ฟังก์ชัน next ซ้ำๆอย่างต่อเนื่องnext เป็นคำสั่งสำคัญสำหรับอิเทอเรเตอร์และเจเนอเรเตอร์ มีหน้าที่สั่งให้เจเนอเรเตอร์เริ่มทำงานโดยในการใช้ครั้งแรกจะเริ่มอ่านโค้ด จากเริ่มต้นไปจนกว่าจะเจอคำสั่ง yield แล้วก็ส่งค่าคืนกลับ แล้วฟังก์ชันก็จะหยุดทำงาน และจะทำงานต่อเมื่อใช้ฟังก์ชัน next อีกครั้งgenfib ที่นิยามไว้ในตัวอย่างก่อนหน้านี้
genji = genfib(5)
print(next(genji)) # ได้ 1
print(next(genji)) # ได้ 1
print(next(genji)) # ได้ 2
print(next(genji)) # ได้ 3
print(next(genji)) # ได้ 5
next กับเจเนอเรเตอร์แล้วจะเป็นการสั่งให้มันคืนค่าออกมาทีละตัวเช่นเดียวกับเวลาที่ใช้ forfor กับเจเนอเรเตอร์หรืออิเทอเรเตอร์นั้น ก็คือการเรียกใช้ next ไปเรื่อยๆจนกว่าจะใช้ไม่ได้แล้วนั่นเองgenfib ที่เราทำไว้นี้จะให้ตัวเลขออกมาตามลำดับเป็นจำนวนเท่ากับเลขที่ใส่ลงไป ในตัวอย่างนี้เราใส่ไป 5 ดังนั้นจึงสามารถใช้คำสั่ง next ได้ ๕ ครั้งnext ครั้งต่อไปโค้ดจะวิ่งต่อไปจนสุดฟังก์ชันโดยที่หา yield ตัวต่อไปไม่เจอ เมื่อเป็นแบบนี้โปรแกรมจะเกิดข้อผิดพลาดขึ้นมาnext ต่อจากตัวอย่างนี้
print(next(genji)) # ได้ StopIteration
StopIteration ซึ่งมีความหมายว่าสิ้นสุดการทำงานของอิเทอเรเตอร์หรือเจเนอเรเตอร์นั้นแล้วdef และ yield นั้น StopIteration จะเกิดขึ้นเมื่อฟังก์ชันถูกอ่านไปจนถึงสุดโดยที่ไม่เจอคำสั่ง yield อีกแล้วfor เมื่อมีการวนซ้ำมาจนหา yield ไม่เจอ สิ่งที่จะเกิดขึ้นก็คือหยุดการวนซ้ำโดยที่ไม่เกิดข้อผิดพลาดขึ้นStopIteration ก็เกิดขึ้นเวลาที่ใช้ for เช่นกัน เพียงแต่โปรแกรมจะตอบสนองด้วยการปล่อยให้ผ่านไปแล้วตัดออกจากวังวนแทน เหมือนกับการใช้ try และ exceptfor อาจมีความหมายเท่ากับการเขียนโค้ดในลักษณะเช่นนี้
genjisama = genfib(7)
while(1):
try:
print(next(genjisama), end='::')
except StopIteration:
break
next ไปจนสุดแล้วเกิด StopIteration จึงหลุดออกไปด้วยคำสั่ง breakfor จนจบแล้วหากใช้ next ต่อก็จะเกิดข้อผิดพลาดขึ้นเช่นกัน
genjisan = genfib(5)
for g in genji:
print(g)
print(next(genjisan)) # ได้ StopIteration
for ซ้ำกับเจเนอเรเตอร์ตัวเดิมหลังจากที่ใช้ for ไปแล้วครั้งหนึ่งจะพบว่า for ตัวหลังไม่ทำงาน เพียงแต่จะไม่เกิดข้อผิดพลาดขึ้น
genjikun = genfib(5)
for g in genjikun:
print(g)
for h in genjikun: # ไม่ทำงาน
print(h)
for กี่ครั้งก็ได้ เพราะจะกลับมาเริ่มไล่ใหม่ตั้งแต่ต้นทุกครั้งที่เริ่มใช้ forfor ไม่จำเป็นว่ามันจะถูกเริ่มตั้งแต่ต้นเสมอ ถ้าหากว่ามันมีการใช้ next มาแล้วก่อนหน้านั้นก็จะเริ่มไล่ต่อจากตรงนั้นต่อเลย
genjichan = genfib(5)
print(next(genjichan)) # ครั้งที่ 1
for h in genjichan: # ครั้งที่ 2,3,4,5
print(h)
def mugen(x):
i = 1
while(1): # วนซ้ำแบบไม่มีเงื่อนไข
yield x*i
i += 1
while อย่างไม่มีที่สิ้นสุด อยากเรียกใช้กี่ครั้งก็ตามที่ต้องการ จะใช้ next กี่ครั้งก็ยังสามารถใช้ได้อีกเรื่อยๆ
mu = mugen('ก')
for i in range(5):
print(next(mu), end='=')
ก=กก=กกก=กกกก=กกกกก=
for กับอิเทอเรเตอร์แบบนี้ไม่ได้ ถ้าไม่มีเงื่อนไขสำหรับให้หยุดกลางคัน เพราะจะเกิดวังวนที่ไม่มีที่สิ้นสุดbreak ช่วย เช่น
for mu in mugen('ม'):
print(mu, end='=')
if(len(mu)>7): break
ม=มม=มมม=มมมม=มมมมม=มมมมมม=มมมมมมม=มมมมมมมม=
map, filter หรือ .join ก็ไม่ได้เช่นกันdef gennap():
n = 0
while(1):
n += 1
yield n
nap = gennap()
khamtop = int(input('ยานอวกาศลำแรกของโลกถูกส่งออกไปในปี ค.ศ. ใด: '))
while(khamtop!=1957):
if(khamtop>1957): print('เร็วกว่านั้น') # กรณีตอบเลขสูงไป
else: print('ช้ากว่านั้น') # กรณีตอบเลขต่ำไป
print('คุณตอบไปแล้ว %d ครั้ง'%next(nap))
khamtop = int(input('ตอบใหม่: '))
else: print('คำตอบถูกต้อง')
next ซึ่งจะคืนค่าที่สูงขึ้นเรื่อยๆทีละ 1 ในแต่ละรอบ การวนซ้ำจะสิ้นสุดลงเมื่อตอบคำถามถูกต้องfor และวงเล็บเหลี่ยม อย่างที่กล่าวถึงตอนท้ายบทที่ ๙
print([i**2 for i in range(7)]) # ได้ [0, 1, 4, 9, 16, 25, 36]
{ } แทนที่จะใช้วงเล็บเหลี่ยม ก็จะได้เซ็ตแทน
print({i**2 for i in range(7)}) # ได้ {0, 1, 4, 36, 9, 16, 25}
( ) ก็จะได้ออกมาเป็นเจเนอเรเตอร์
genki = (i**2 for i in range(7))
print(genki) # ได้ at 0x112583938>
print(list(genki)) # ได้ [0, 1, 4, 9, 16, 25, 36]
(i**2 for i in range(10000000)) # สร้างเจเนอเรเตอร์ เสร็จในพริบตา
[i**2 for i in range(10000000)] # สร้างลิสต์ ต้องรอนาน
{i**2 for i in range(10000000)} # สร้างเซ็ต นานที่สุด
for แล้ว เจเนอเรเตอร์จะมาเสียเวลาตอนที่ที่วนซ้ำเพราะต้องสร้างข้อมูลขึ้นมาใหม่ ทำให้เวลาที่ใช้ทั้งหมด (= เวลาเตรียมตัว + เวลาที่วนซ้ำ) โดยรวมแล้วเจเนอเรเตอร์จะเร็วกว่าไม่มากbreak แบบนี้เจเนอเรเตอร์จะประหยัดเวลากว่ามากเพราะเสียเวลาสร้างข้อมูลแค่เท่ากับ จำนวนครั้งที่วนไปเท่านั้นติดตามอัปเดตของบล็อกได้ที่แฟนเพจ