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