ใน
บทที่แล้วได้แนะนำสิ่งที่เรียกอิเทอเรอเตอร์ไปแล้ว และได้พูดถึงวิธีสร้างอิเทอเรเตอร์อย่างง่ายที่เรียกว่าเจเนอเรเตอร์
แต่เจเนอเรเตอร์นั้นเป็นเพียงอิเทอเรเตอร์ชนิดหนึ่งที่มีรูปแบบการสร้างที่ค่อนข้างง่าย
ส่วนในบทนี้เราจะพูดถึงการสร้างอิเทอเรเตอร์ขึ้นจากคลาส ซึ่งเป็นวิธีที่เข้าใจค่อนข้างยากกว่า แต่ว่าก็ทำให้สามารถปรับแต่งทำอะไรเพิ่มเติมได้มากมายกว่า ในขณะที่เจเนอเรเตอร์นั้นจะมีรูปแบบค่อนข้างตายตัวกว่า
การสร้างคลาสของอิเทอเรเตอร์ การสร้างคลาสของอิเทอเรเตอร์ก็ทำได้เช่นเดียวกับคลาสของออบเจ็กต์ทั่วไป แต่ว่าจำเป็นจะต้องมีเมธอดที่สำคัญ ๒ ตัวรวมอยู่ด้วย นั่นคือ
__iter__
และ
__next__
ขอเริ่มจากการยกตัวอย่างการสร้างคลาสอิเทอเรเตอร์อย่าง ง่ายๆสั้นๆที่สุดก่อนที่จะเริ่มอธิบายว่า
__iter__
กับ
__next__
มีหน้าที่ไว้ทำอะไร
class Iternap:
def __init__(self):
self.n = 0
def __iter__(self):
return self
def __next__(self):
self.n += 1
return self.n
คลาส
Iternap
ในตัวอย่างนี้คือคลาสของอิเทอเรเตอร์ที่ใช้นับจำนวนตัวเลข โดยจะคืนค่าตัวเลข
1, 2, 3, ...
ไปเรื่อยๆ มีลักษณะการทำงานเหมือนกับเจเนอเรเตอร์จากฟังก์ชัน
gennap
ซึ่งเขียนถึงไปใน
บทที่แล้ว (ให้ย้อนกลับไปดูแล้วเทียบกัน ไม่ขอยกมาเขียนซ้ำ)
ลองเริ่มนำมาใช้งานดูเลย
nap = Iternap()
print(next(nap)) # ได้ 1
print(next(nap)) # ได้ 2
print(next(nap)) # ได้ 3
จะเห็นว่าเมื่อใช้ฟังก์ชัน
next
จะมีการคืนค่าตัวเลขที่เพิ่มขึ้นตามลำดับ นี่เป็นอิเทอเรเตอร์ที่ให้ผลเช่นเดียวกับเจเนอเรเตอร์
คราวนี้มาลองดูโค้ดที่ใช้ในการสร้างคลาส
Iternap
จะเห็นว่ามีการนิยามเมธอดขึ้นมา ๓ อัน คือ
__init__
,
__iter__
และ
__next__
เมธอด
__init__
นั้นได้อธิบายไปใน
บทที่ ๒๒ แล้วว่าเป็นเมธอดที่จะทำงานเมื่อคลาสถูกสร้างขึ้น ในที่นี้ใส่คำสั่งลงไปเพียงอย่างเดียวคือให้แอตทริบิวต์
n
เท่ากับ
0
ส่วนเมธอด
__next__
คือเมธอดที่จะทำงานเมื่อมีการใช้ฟังก์ชัน
next
นั่นเอง ซึ่งก็คล้ายกับเมธอดทั้งหลายที่ได้พูดถึงไปใน
บทที่ ๒๔ เช่นเมธอด
__len__
,
__str__
และ
__bool__
ทำงานเมื่อใช้ฟังก์ชัน
len
,
str
และ
bool
ตามลำดับ
ลองพิมพ์
print(nap.__next__()) # ได้ 4
print(nap.__next__()) # ได้ 5
จะเห็นว่า
nap.__next__()
ทำงานเหมือนกับ
next(nap)
นั่นคือเพิ่มค่าแอตทริบิวต์
n
ทีละ
1
แล้วคืนค่ากลับมา
ในเจเนอเรเตอร์เองก็เช่นกัน ความจริงแล้วฟังก์ชัน
next
ที่ใช้กันนี้เป็นแค่ฟังก์ชันสำหรับเรียกเมธอด
__next__
เท่านั้น ในเจเนอเรเตอร์นั้นแม้เราจะไม่ได้นิยามเมธอด
__next__
โดยตรง แต่มันถูกนิยามให้โดยอัตโนมัติ
ขอเน้นว่าคำสั่งสำหรับส่งค่าคืนกลับ ที่ใช้ในเมธอด
__next__
คือ
return
เหมือนฟังก์ชันทั่วไป ไม่ใช่
yield
เหมือนอย่างที่ใช้ในฟังก์ชันสร้างเจเนอเรเตอร์ คำสั่ง
yield
จะใช้กับเจเนอเรเตอร์เท่านั้น
***ในไพธอน 2 เมธอด
__next__
นี้ชื่อ
next
เฉยๆ ไม่มีขีดล่างสองตัวขนาบ
>>> รายละเอียด เมธอด __iter__ ส่วนเมธอด
__iter__
นั้นก็ทำนองเดียวกันกับ
__next__
มันคือเมธอดที่จะถูกเรียกใช้โดยฟังก์ชันชื่อ
iter
ฟังก์ชัน
iter
นี้ไม่ได้กล่าวถึงตั้งแต่บทที่แล้ว ความจริงแล้วเมธอด
__iter__
นี้อาจไม่จำเป็นต้องมีก็ได้หากเราจะใช้แต่ฟังก์ชัน
next
เพื่อเดินหน้าอิเทอเรเตอร์ต่อไปเรื่อยๆ
แต่
__iter__
จะจำเป็นในกรณีที่ต้องการใช้ใน
for
หรือฟังก์ชันจำพวก
map
และ
filter
เวลาที่ใช้คำสั่ง
for
เมธอด
__iter__
จะถูกเรียกใช้โดยอัตโนมัติเพื่อเป็นการยืนยันว่าออบเจ็กต์นี้เป็นอิเทอเรเตอร์
หากไม่มีการนิยามเมธอด
__iter__
เวลาใช้
for
จะเกิดข้อผิดพลาดขึ้นทันที
class Itermaidai:
def __init__(self):
self.n = 0
def __next__(self):
self.n += 1
return self.n
for it in Itermaidai():
print(it)
ได้
TypeError: 'Itermaidai' object is not iterable
การนิยามเมธอด
__iter__
นั้นปกติก็จะแค่ใส่คำสั่ง
return
ให้ส่งค่าคืนกลับเป็นตัวออบเจกต์ของอิเทอเรเตอร์นั้นเอง นี่เป็นการเขียนที่ค่อนข้างตายตัว โดยทั่วไปไม่มีความจำเป็นจะต้องใส่อะไรมากกว่านั้น แต่หากต้องการให้มีคำสั่งบางอย่างถูกทำก่อนที่จะเริ่มทำงานใน
for
ก็สามารถใส่คำสั่งอะไรบางอย่างไปก่อน
return
หลังจากที่เรียก
__iter__
แล้ว ต่อมาก็จะมีการเรียก
__next__
แล้วก็เริ่มการทำงานวนซ้ำใน
for
แล้วก็เรียก
__next__
ไปเรื่อยๆทุกครั้ง
ลองเอา
Iternap
ที่นิยามตอนแรกสุดนี้มาใช้กับ
for
ดู
for it in Iternap():
if(it>10): break
print(it,end='>')
ได้
1>2>3>4>5>6>7>8>9>10>
ซึ่งจะมีค่าเทียบเท่ากับการเขียนแบบนี้
it = Iternap()
it.__iter__()
i = 1
while(i<=10):
print(it.__next__(),end='>')
i += 1
หรือหากใช้ฟังก์ชัน
iter
กับ
next
แทนเมธอด
__iter__
และ
__next__
ก็จะเป็นแบบนี้
it = Iternap()
iter(it)
i = 1
while(i<=10):
print(next(it),end='>')
i += 1
จะเห็นว่าที่จริง
it.__iter__()
หรือ
iter(it)
นั้นไม่ได้จำเป็นต้องใส่ เพราะไม่ได้ทำหน้าที่อะไร ที่ใส่ไว้ในที่นี้ก็เพื่อให้เทียบเท่ากับการใช้
for
เท่านั้น
อนึ่ง จะเห็นว่าในการใช้
for
จะต้องใส่
break
ไว้เพราะอิเทอเรเตอร์ที่นิยามขึ้นในครั้งนี้ไม่ได้กำหนดจุดจบเอาไว้
การกำหนดจุดสิ้นสุดของอิเทอเรเตอร์ ในกรณีของเจเนอเรเตอร์การสิ้นสุดจะเกิดขึ้นเมื่อฟังก์ชันอ่านโค้ดจนถึงสุดโดยที่ไม่เจอ
yield
แต่สำหรับอิเทอเรเตอร์ที่เราสร้างขึ้นมาเองจากคลาสนั้นจะใช้วิธีการส่งความผิด พลาดชนิด
StopIteration
ไปด้วยคำสั่ง
raise
ภายในเมธอด
__next__
ตัวอย่าง ลองสร้างคลาสของอิเทอเรเตอร์สำหรับนับถอยหลัง
class Iternapthoilang:
def __init__(self,n):
self.n = n+1 # ตั้งค่าจำนวนเริ่มต้นตามจำนวนที่ป้อนเข้าไปในพารามิเตอร์
def __iter__(self):
return self
def __next__(self):
self.n -= 1 # ลดลงทีละ 1 ทุกรอบ
if(self.n==0):
raise StopIteration # หากเหลือ 0 ให้หยุด
return self.n
for it in Iternapthoilang(10):
print(it,end='>')
จะเห็นว่าตัวอย่างนี้ในเมธอด
__next__
มีการตั้งให้ถ้า
n
เหลือ
0
จะทำคำสั่ง
raise
เพื่อส่งข้อผิดพลาดชนิด
StopIteration
ออกไปเพื่อเป็นสัญญาณให้สิ้นสุดการวนซ้ำ
ข้อผิดพลาดในที่นี้ไม่ใช่ ข้อผิดพลาดจริงๆแต่เป็นแค่ตัวบอกว่าให้หยุดการทำซ้ำเท่านั้น หากเป็นในเจเนอเรเตอร์คำสั่งนี้จะเกิดขึ้นเองเมื่อไม่เจอ
yield
แต่หากสร้างคลาสขึ้นเองเราต้องสร้าง
StopIteration
ขึ้นเอง
พออิเทอเรเตอร์มีจุดสิ้นสุดแล้วแบบนี้ก็สามารถนำมาใช้กับ
for
ได้ง่ายโดยไม่ต้องใส่
break
รวมถึงสามารถใช้กับฟังก์ชันอย่าง
map
หรือ
filter
ได้
ตอนนี้เราสามารถอิเทอเรเตอร์สำหรับสร้างลำดับฟีโบนัชชีเช่นเดียวกับที่ใช้เจเนอเรเตอร์ในบทที่แล้วสร้างขึ้นได้
class Iterfib:
def __init__(self,n):
self.n = n
self.a = 1
self.b = 1
self.i = 1
def __iter__(self):
return self
def __next__(self):
if(self.i<=self.n):
x = self.a
self.a, self.b = self.b, self.a+self.b
self.i += 1
return x
else:
raise StopIteration
for it in Iterfib(7):
print(it,end='>>')
ได้
1>>1>>2>>3>>5>>8>>13>>
การทำให้อิเทอเรเตอร์เริ่มใหม่ทุกครั้งที่เริ่มวนซ้ำ ปกติแล้วถ้าเป็นเจเนอเรเตอร์ เมื่อออบเจ็กต์ที่เป็นเจเนอเรเตอร์ถูกสร้างขึ้นมามันจะใช้วนซ้ำใน
for
ได้แค่ครั้งเดียว หลังจากนั้นจะใช้ต่อไม่ได้
อิเทอเรเตอร์คลาส
Iterfib
ที่สร้างขึ้นมาในตัวอย่างที่แล้วเองก็มีคุณสมบัติแบบเดียวกัน
ลอง
ite = Iterfib(7)
for it in ite:
print(it,end='~~')
for it in ite:
print(it,end='^^')
จะได้
1~~1~~2~~3~~5~~8~~13~~
นั่นคือจะเห็นว่า
for
ตัวหลังไม่ทำงาน เพราะออบเจ็กต์อิเทอเรเตอร์
ite
นั้นได้ถูกใช้งานหมดตั้งแต่
for
ตัวแรกแล้ว
แต่เราสามารถเขียนให้อิเทอเรเตอร์ทำงานใหม่ทุกครั้งที่เริ่ม
for
ได้ โดยการแก้โครงสร้างคลาสเล็กน้อยดังนี้
class Iterfib:
def __init__(self,n):
self.n = n
def __iter__(self):
self.a = 1 # กำหนดค่าเริ่มต้น
self.b = 1
self.i = 1
return self
def __next__(self):
if(self.i<=self.n):
x = self.a
self.a, self.b = self.b, self.a+self.b
self.i += 1
return x
else:
raise StopIteration
ite = Iterfib(7)
for it in ite:
print(it,end='~~')
for it in ite:
print(it,end='^^')
ได้
1~~1~~2~~3~~5~~8~~13~~1^^1^^2^^3^^5^^8^^13^^
ข้อแตกต่างจาก
Iterfib
ตัวเดิมก็คือจะเห็นว่าส่วนที่กำหนดค่าแอตทริบิวต์
a
,
b
และ
i
ตอนเริ่มต้นนั้นย้ายจากในเมธอด
__init__
มาอยู่ในเมธอด
__iter__
แทน
อย่างที่บอกไปตอนแรกว่าเมธอด
__iter__
นั้นไม่มีความจำเป็นต้องเขียนอะไรนอกจาก
return self
แต่หากมีสิ่งที่ต้องการให้โปรแกรมทำก่อนที่จะเริ่มการใช้
for
ละก็จะต้องใส่คำสั่งลงในนี้ เพราะ
__iter__
จะถูกเรียกทุกครั้งที่เริ่ม
for
นั่นเอง ต่างจาก
__init__
ที่ถูกเรียกครั้งเดียวตอนสร้างออบเจ็กต์
ในที่นี้จึงใส่คำสั่งให้แอตทริบิวต์
a
,
b
และ
i
กลับมายังจุดเริ่มต้น ทำให้ทุกครั้งที่เริ่มวนซ้ำด้วย
for
อิเทอเรเตอร์จะถูกเริ่มนับใหม่ ทำให้สามารถใช้งานกี่ครั้งก็ได้ เช่นเดียวกับการใช้ลิสต์วนซ้ำใน
for
เมธอด __reversed__ และ __len__ ปกติแล้วออบเจ็กต์ชนิดลำดับเช่นลิสต์, ทูเพิล และเรนจ์ จะมีเมธอด
__reversed__
ติดตัวอยู่ นี่เป็นเมธอดที่จะถูกเรียกใช้เมื่อใช้ฟังก์ชัน
reversed
ซึ่งจะได้อิเทอเรเตอร์ที่นำเอาสมาชิกในลำดับมาไล่จากท้าย
แต่สำหรับอิเทอเรเตอร์นั้นโดยปกติจะไม่สามารถใช้ฟังก์ชัน
reversed
ได้ อย่างไรก็ตามก็สามารถทำให้ใช้งานได้โดยใส่เมธอด
__reversed__
ลงไปตอนสร้าง
นอกจากนี้ก็ยังมีฟังก์ชัน
len
ซึ่งสามารถใช้วัดความยาวได้แค่ลิสต์ แต่หากจะให้อิเทอเรเตอร์ที่สร้างมาใช้ได้ก็ต้องใส่เมธอด
__len__
ให้
ตัวอย่าง การสร้างอิเทอเรเตอร์สำหรับสร้างตัวเลขไล่ตั้งแต่อาร์กิวเมนต์ตัวแรกถึงตัวหลัง โดยใส่เมธอด
__len__
และ
__reversed__
ลงไปเพื่อให้สามารถวัดความยาวและไล่ถอยหลังได้
class Iternaplai:
def __init__(self,a=0,b=100):
self.a = a
self.b = b
def __iter__(self):
self.i = self.a-1.
return self
def __next__(self):
self.i += 1
if(self.i>self.b):
raise StopIteration
return self.i
def __len__(self):
return len([x for x in self])
def __reversed__(self):
return reversed([x for x in self])
print(len(Iternaplai(11,19))) # ได้ 9
print(list(reversed(Iternaplai(11,19)))) # ได้ [19.0, 18.0, 17.0, 16.0, 15.0, 14.0, 13.0, 12.0, 11.0]
การใช้ฟังก์ชัน iter เพื่อสร้างอิเทอเรเตอร์จากออบเจ็กต์ชนิดกลุ่ม ฟังก์ชัน
iter
นั้นนอกจากจะใช้กับออบเจ็กต์ชนิดกลุ่มได้ ซึ่งผลที่ได้ก็คือจะคืนตัวออบเจ็กต์นั้นกลับมาในรูปของอิเทอเรเตอร์
ลองใช้กับลิสต์ดูจะได้ข้อมูลที่มีคลาสเป็น
list_iterator
list_it = iter([1,2,3])
print(type(list_it)) # ได้ <class 'list_iterator'>
print(list_it) # ได้
หรือจะเขียน
list_it = [1,2,3].__iter__()
ก็ได้ เพราะฟังก์ชัน
iter
คือการเรียกใช้เมธอด
__iter__
และเมธอดนี้มีอยู่ในออบเจ็กต์ชนิดกลุ่มอยู่แล้ว
อิเทอเรเตอร์ที่ได้มานี้จะเป็นตัววนซ้ำเพื่อส่งค่าคืนกลับมาเป็นสมาชิกในนั้นทีละตัว
การใช้งานของมันก็คือใช้กับ
for
เช่นเดียวกับตัวลิสต์ที่เป็นต้นแบบ และผลที่ได้ก็เหมือนกัน
for i in list_it:
print(i, end='-')
ได้
1-2-3-
เพียงแต่ว่าหากจะแปลงเป็นอิเทอเรเตอร์เพื่อจะมาใช้ใน
for
ก็อาจไม่ค่อยมีประโยชน์นัก เพราะตัวลิสต์เองสามารถใช้ใน
for
ได้โดยตรงอยู่แล้ว
ดังนั้น
for i in iter([1,2,3]):
กับ
for i in [1,2,3]:
มีค่าเท่ากัน
ลองใช้กับทูเพิล, เซ็ต, ดิกชันนารี, สายอักขระ และเรนจ์ ผลที่ได้ก็จะเป็นในทำนองเดียวกัน
tuple_it = iter((4,5,6))
print(tuple_it) # ได้
set_it = iter({7,8,9})
print(set_it) # ได้
dict_it = iter({'ก':1,'ข':2,'ค':3})
print(dict_it) # ได้
str_it = iter('กขค')
print(str_it) # ได้
range_it = iter(range(9))
print(range_it) # ได้
ในทางกลับกันอิเทอเรเตอร์เหล่านี้ก็สามารถแปลงกลับเป็นข้อมูลชนิดกลุ่มได้ แต่จะใช้ได้แค่ครั้งเดียว
print(set(tuple_it)) # ได้ {4, 5, 6}
print(tuple(range_it)) # ได้ (0, 1, 2, 3, 4, 5, 6, 7, 8)
print(list(range_it)) # ได้ [] เพราะถูกใช้ได้ครั้งเดียว
อ้างอิง