__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 จะใช้กับเจเนอเรเตอร์เท่านั้น__next__ นี้ชื่อ next เฉยๆ ไม่มีขีดล่างสองตัวขนาบ__iter__ นั้นก็ทำนองเดียวกันกับ __next__ มันคือเมธอดที่จะถูกเรียกใช้โดยฟังก์ชันชื่อ iteriter นี้ไม่ได้กล่าวถึงตั้งแต่บทที่แล้ว ความจริงแล้วเมธอด __iter__ นี้อาจไม่จำเป็นต้องมีก็ได้หากเราจะใช้แต่ฟังก์ชัน next เพื่อเดินหน้าอิเทอเรเตอร์ต่อไปเรื่อยๆ__iter__ จะจำเป็นในกรณีที่ต้องการใช้ใน for หรือฟังก์ชันจำพวก map และ filterfor เมธอด __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 ไว้เพราะอิเทอเรเตอร์ที่นิยามขึ้นในครั้งนี้ไม่ได้กำหนดจุดจบเอาไว้yieldStopIteration ไปด้วยคำสั่ง 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__ ติดตัวอยู่ นี่เป็นเมธอดที่จะถูกเรียกใช้เมื่อใช้ฟังก์ชัน 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 นั้นนอกจากจะใช้กับออบเจ็กต์ชนิดกลุ่มได้ ซึ่งผลที่ได้ก็คือจะคืนตัวออบเจ็กต์นั้นกลับมาในรูปของอิเทอเรเตอร์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)) # ได้ [] เพราะถูกใช้ได้ครั้งเดียว
ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ