ใน
บทที่แล้วได้แนะนำสิ่งที่เรียกอิเทอเรอเตอร์ไปแล้ว และได้พูดถึงวิธีสร้างอิเทอเรเตอร์อย่างง่ายที่เรียกว่าเจเนอเรเตอร์
แต่เจเนอเรเตอร์นั้นเป็นเพียงอิเทอเรเตอร์ชนิดหนึ่งที่มีรูปแบบการสร้างที่ค่อนข้างง่าย
ส่วนในบทนี้เราจะพูดถึงการสร้างอิเทอเรเตอร์ขึ้นจากคลาส ซึ่งเป็นวิธีที่เข้าใจค่อนข้างยากกว่า แต่ว่าก็ทำให้สามารถปรับแต่งทำอะไรเพิ่มเติมได้มากมายกว่า ในขณะที่เจเนอเรเตอร์นั้นจะมีรูปแบบค่อนข้างตายตัวกว่า
การสร้างคลาสของอิเทอเรเตอร์ การสร้างคลาสของอิเทอเรเตอร์ก็ทำได้เช่นเดียวกับคลาสของออบเจ็กต์ทั่วไป แต่ว่าจำเป็นจะต้องมีเมธอดที่สำคัญ ๒ ตัวรวมอยู่ด้วย นั่นคือ
__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)) # ได้ [] เพราะถูกใช้ได้ครั้งเดียว
อ้างอิง