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
แทน return
yield
อยู่ในฟังก์ชัน ฟังก์ชันนั้นก็จะกลายเป็นฟังก์ชันสร้างเจเนอเรเตอร์ทันที และคุณสมบัติการทำงานจะต่างไปจากฟังก์ชันทั่วไปทันที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
กับเจเนอเรเตอร์แล้วจะเป็นการสั่งให้มันคืนค่าออกมาทีละตัวเช่นเดียวกับเวลาที่ใช้ for
for
กับเจเนอเรเตอร์หรืออิเทอเรเตอร์นั้น ก็คือการเรียกใช้ next
ไปเรื่อยๆจนกว่าจะใช้ไม่ได้แล้วนั่นเองgenfib
ที่เราทำไว้นี้จะให้ตัวเลขออกมาตามลำดับเป็นจำนวนเท่ากับเลขที่ใส่ลงไป ในตัวอย่างนี้เราใส่ไป 5 ดังนั้นจึงสามารถใช้คำสั่ง next
ได้ ๕ ครั้งnext
ครั้งต่อไปโค้ดจะวิ่งต่อไปจนสุดฟังก์ชันโดยที่หา yield
ตัวต่อไปไม่เจอ เมื่อเป็นแบบนี้โปรแกรมจะเกิดข้อผิดพลาดขึ้นมาnext
ต่อจากตัวอย่างนี้
print(next(genji)) # ได้ StopIteration
StopIteration
ซึ่งมีความหมายว่าสิ้นสุดการทำงานของอิเทอเรเตอร์หรือเจเนอเรเตอร์นั้นแล้วdef
และ yield
นั้น StopIteration
จะเกิดขึ้นเมื่อฟังก์ชันถูกอ่านไปจนถึงสุดโดยที่ไม่เจอคำสั่ง yield
อีกแล้วfor
เมื่อมีการวนซ้ำมาจนหา yield
ไม่เจอ สิ่งที่จะเกิดขึ้นก็คือหยุดการวนซ้ำโดยที่ไม่เกิดข้อผิดพลาดขึ้นStopIteration
ก็เกิดขึ้นเวลาที่ใช้ for
เช่นกัน เพียงแต่โปรแกรมจะตอบสนองด้วยการปล่อยให้ผ่านไปแล้วตัดออกจากวังวนแทน เหมือนกับการใช้ try
และ except
for
อาจมีความหมายเท่ากับการเขียนโค้ดในลักษณะเช่นนี้
genjisama = genfib(7)
while(1):
try:
print(next(genjisama), end='::')
except StopIteration:
break
next
ไปจนสุดแล้วเกิด StopIteration
จึงหลุดออกไปด้วยคำสั่ง break
for
จนจบแล้วหากใช้ 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
กี่ครั้งก็ได้ เพราะจะกลับมาเริ่มไล่ใหม่ตั้งแต่ต้นทุกครั้งที่เริ่มใช้ for
for
ไม่จำเป็นว่ามันจะถูกเริ่มตั้งแต่ต้นเสมอ ถ้าหากว่ามันมีการใช้ 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
แบบนี้เจเนอเรเตอร์จะประหยัดเวลากว่ามากเพราะเสียเวลาสร้างข้อมูลแค่เท่ากับ จำนวนครั้งที่วนไปเท่านั้นติดตามอัปเดตของบล็อกได้ที่แฟนเพจ