35. Cara Kerja Iterasi: Iterable dan Iterator
Sepanjang buku ini, kamu sudah menggunakan loop for untuk melakukan iterasi pada list, string, dictionary, dan koleksi lainnya. Kamu sudah menulis kode seperti for item in my_list: berkali-kali. Tapi apa sebenarnya yang terjadi di balik layar saat Python mengeksekusi loop for? Bagaimana Python tahu cara melangkah melalui berbagai tipe koleksi?
Di bab ini, kita akan membahas protokol iterasi (iteration protocol) Python—mekanisme yang membuat loop for bekerja. Kamu akan belajar tentang iterable (iterables) (objek yang bisa kamu loop) dan iterator (iterators) (objek yang benar-benar melakukan proses melangkah melalui nilai). Memahami perbedaan ini akan memperdalam pengetahuanmu tentang cara kerja Python dan mempersiapkanmu untuk bekerja dengan generator di Bab 36.
35.1) Apa Artinya Sebuah Objek Bersifat Iterable
35.1.1) Konsep Iterabilitas
Sebuah iterable (iterable) adalah objek Python apa pun yang bisa diloop dengan loop for. Saat kita bilang "diloop," maksudnya Python bisa mengambil item dari objek tersebut satu per satu, secara berurutan.
Kamu sudah bekerja dengan banyak iterable:
# List bersifat iterable
numbers = [1, 2, 3, 4, 5]
for num in numbers:
print(num) # Output: 1, 2, 3, 4, 5 (pada baris terpisah)
# String bersifat iterable
text = "Python"
for char in text:
print(char) # Output: P, y, t, h, o, n (pada baris terpisah)
# Dictionary bersifat iterable (secara default pada key)
student = {"name": "Alice", "age": 20, "grade": "A"}
for key in student:
print(key) # Output: name, age, grade (pada baris terpisah)Semua objek ini—list, string, dictionary, tuple, set, range, dan file—adalah iterable karena mereka mendukung protokol iterasi (iteration protocol) Python (seperangkat aturan yang memungkinkan Python melakukan loop pada mereka).
35.1.2) Apa yang Membuat Sebuah Objek Menjadi Iterable
Agar sebuah objek menjadi iterable, objek tersebut harus mengimplementasikan metode spesial bernama __iter__(). Metode ini mengembalikan sebuah objek iterator (iterator). Jangan khawatir soal detailnya dulu—kita akan membahas iterator di bagian berikutnya.
Kamu bisa mengecek apakah sebuah objek iterable dengan mencoba mendapatkan iterator darinya menggunakan fungsi bawaan iter():
# Menguji apakah objek bersifat iterable
numbers = [1, 2, 3]
iterator = iter(numbers) # Berhasil - list bersifat iterable
print(type(iterator)) # Output: <class 'list_iterator'>
text = "Hello"
iterator = iter(text) # Berhasil - string bersifat iterable
print(type(iterator)) # Output: <class 'str_iterator'>
# Mencoba dengan objek yang tidak iterable
value = 42
try:
iterator = iter(value) # Gagal - integer tidak iterable
except TypeError as e:
print(f"Error: {e}") # Output: Error: 'int' object is not iterableSaat kamu memanggil iter() pada objek iterable, Python memanggil metode __iter__() milik objek tersebut dan mengembalikan sebuah iterator. Jika objek itu tidak memiliki metode ini, kamu akan mendapatkan TypeError.
35.1.3) Iterable vs Sequence
Penting untuk dipahami bahwa tidak semua iterable adalah sequence. Sequence adalah tipe iterable spesifik yang mendukung indexing dan punya urutan yang terdefinisi.
# Sequence mendukung indexing
my_list = [10, 20, 30]
print(my_list[0]) # Output: 10
my_string = "Python"
print(my_string[2]) # Output: t
# Set bersifat iterable tetapi BUKAN sequence (tanpa indexing, tanpa urutan yang dijamin)
my_set = {1, 2, 3}
for item in my_set:
print(item) # Berhasil - set bersifat iterable
# Tapi indexing tidak berfungsi
try:
print(my_set[0]) # Gagal - set tidak mendukung indexing
except TypeError as e:
print(f"Error: {e}") # Output: Error: 'set' object is not subscriptablePerbedaan kunci: Semua sequence (list, tuple, string, range) adalah iterable, tetapi tidak semua iterable adalah sequence. Set dan dictionary adalah iterable tetapi bukan sequence karena mereka tidak mendukung indexing.
35.1.4) Kenapa Iterabilitas Itu Penting
Memahami iterabilitas membantu kamu:
- Tahu apa yang bisa kamu loop: Semua iterable bisa dipakai dengan loop
for - Memahami pesan error: "object is not iterable" berarti kamu tidak bisa memakainya dalam loop
for - Menggunakan comprehension: List, set, dan dictionary comprehension bekerja dengan iterable apa pun
- Bekerja dengan fungsi bawaan: Banyak built-in seperti
sum(),max(),min(), dansorted()menerima iterable apa pun
# Semua ini bekerja karena menerima iterable
numbers = [1, 2, 3, 4, 5]
print(sum(numbers)) # Output: 15
text = "Python"
print(max(text)) # Output: y (paling tinggi secara alfabet)
# Bahkan bisa bekerja dengan set
unique_values = {10, 5, 20, 15}
print(sorted(unique_values)) # Output: [5, 10, 15, 20]35.2) Iterator Sehari-hari di Python (File, Range, Dictionary, dan Lainnya)
35.2.1) Apa Itu Iterator
Sebuah iterator (iterator) adalah objek yang merepresentasikan aliran data. Ia mengembalikan satu nilai setiap kali kamu meminta item berikutnya. Setelah iterator mengembalikan semua nilainya, iterator tersebut habis (exhausted) dan tidak bisa digunakan ulang.
Bayangkan iterator seperti bookmark di buku:
- Ia mengingat kamu berada di posisi mana dalam urutan
- Kamu bisa meminta item berikutnya
- Begitu kamu mencapai akhir, kamu tidak bisa kembali tanpa membuat iterator baru
Perbedaan utama antara iterable dan iterator:
- Iterable (iterable) adalah sesuatu yang bisa kamu iterasi (seperti list)
- Iterator (iterator) adalah objek yang melakukan iterasi (mekanisme yang melangkah melalui list)
# List adalah sebuah iterable
numbers = [1, 2, 3]
# Mengambil iterator dari iterable
iterator = iter(numbers)
# Iterator adalah objek yang terpisah
print(type(numbers)) # Output: <class 'list'>
print(type(iterator)) # Output: <class 'list_iterator'>35.2.2) Iterator dalam Loop for
Saat kamu menulis loop for, Python otomatis membuat iterator di balik layar:
numbers = [10, 20, 30]
# Yang kamu tulis:
for num in numbers:
print(num)
# Yang Python lakukan secara internal (secara konseptual):
# 1. Memanggil iter(numbers) untuk mendapatkan iterator
# 2. Memanggil next() pada iterator secara berulang
# 3. Berhenti saat iterator melempar StopIterationBerikut tampilan eksplisitnya:
numbers = [10, 20, 30]
# Iterasi manual (yang dilakukan for secara otomatis)
iterator = iter(numbers)
try:
print(next(iterator)) # Output: 10
print(next(iterator)) # Output: 20
print(next(iterator)) # Output: 30
print(next(iterator)) # Would raise StopIteration
except StopIteration:
print("No more items") # Output: No more itemsLoop for menangani exception StopIteration secara otomatis, itulah kenapa kamu tidak pernah melihatnya dalam kode normal.
35.2.3) Objek File sebagai Iterator
Objek file adalah contoh iterator yang sangat bagus. Saat kamu melakukan iterasi pada file, ia membaca satu baris setiap kali:
# Membuat file contoh
with open("students.txt", "w") as file:
file.write("Alice\n")
file.write("Bob\n")
file.write("Charlie\n")
# Membaca file baris demi baris
with open("students.txt", "r") as file:
for line in file:
print(line.strip()) # Output: Alice, Bob, Charlie (pada baris terpisah)Objek file bersifat iterable sekaligus iterator. Mereka mengembalikan dirinya sendiri ketika kamu memanggil iter() pada mereka:
with open("students.txt", "r") as file:
iterator = iter(file)
print(file is iterator) # Output: True (objek yang sama)
# Membaca baris secara manual
print(next(iterator)) # Output: Alice
print(next(iterator)) # Output: Bob
print(next(iterator)) # Output: CharlieIni hemat memori karena Python tidak memuat seluruh file ke memori—ia membaca satu baris setiap kali kamu memintanya.
35.2.4) Objek Range sebagai Iterator
Objek range adalah iterable yang menghasilkan angka sesuai permintaan:
# Range adalah sebuah iterable
numbers = range(1, 4)
print(type(numbers)) # Output: <class 'range'>
# Mengambil iterator dari range
iterator = iter(numbers)
print(type(iterator)) # Output: <class 'range_iterator'>
# Menggunakan iterator
print(next(iterator)) # Output: 1
print(next(iterator)) # Output: 2
print(next(iterator)) # Output: 3Range hemat memori karena mereka tidak menyimpan semua angka di memori—mereka menghitung setiap angka saat diminta:
# Range ini merepresentasikan 1 juta angka tetapi memakai memori minimal
large_range = range(1000000)
print(type(large_range)) # Output: <class 'range'>
# Mengambil iterator
iterator = iter(large_range)
print(next(iterator)) # Output: 0
print(next(iterator)) # Output: 1
# ... bisa lanjut untuk 1 juta nilai35.2.5) Iterator Dictionary
Dictionary menyediakan iterator berbeda untuk key, value, dan item:
student = {"name": "Alice", "age": 20, "grade": "A"}
# Iterasi pada key (default)
for key in student:
print(key) # Output: name, age, grade (pada baris terpisah)
# Mengambil iterator keys secara eksplisit
keys_iterator = iter(student.keys())
print(next(keys_iterator)) # Output: name
print(next(keys_iterator)) # Output: age
# Iterasi pada value
values_iterator = iter(student.values())
print(next(values_iterator)) # Output: Alice
print(next(values_iterator)) # Output: 20
# Iterasi pada item (pasangan key-value)
items_iterator = iter(student.items())
print(next(items_iterator)) # Output: ('name', 'Alice')
print(next(items_iterator)) # Output: ('age', 20)35.2.6) Iterator Bisa Habis (Exhaustible)
Properti penting dari iterator adalah mereka hanya bisa dipakai sekali. Setelah habis, mereka tidak reset:
numbers = [1, 2, 3]
iterator = iter(numbers)
# Putaran pertama melalui iterator
print(next(iterator)) # Output: 1
print(next(iterator)) # Output: 2
print(next(iterator)) # Output: 3
# Iterator sekarang sudah habis
try:
print(next(iterator)) # Raises StopIteration
except StopIteration:
print("Iterator exhausted") # Output: Iterator exhausted
# Untuk iterasi lagi, buat iterator baru
iterator = iter(numbers)
print(next(iterator)) # Output: 1 (mulai baru)Ini berbeda dari iterable itu sendiri, yang bisa diiterasi berkali-kali:
numbers = [1, 2, 3]
# Iterasi pertama
for num in numbers:
print(num) # Output: 1, 2, 3
# Iterasi kedua (berhasil - membuat iterator baru)
for num in numbers:
print(num) # Output: 1, 2, 335.3) Menggunakan iter() dan next() untuk Melangkah Melalui Iterable
35.3.1) Fungsi iter()
Fungsi iter() menerima sebuah iterable dan mengembalikan sebuah iterator. Ini adalah langkah pertama dalam protokol iterasi:
# Membuat iterator dari berbagai iterable
numbers = [10, 20, 30]
iterator = iter(numbers)
print(type(iterator)) # Output: <class 'list_iterator'>
text = "Hi"
text_iterator = iter(text)
print(type(text_iterator)) # Output: <class 'str_iterator'>
my_set = {1, 2, 3}
set_iterator = iter(my_set)
print(type(set_iterator)) # Output: <class 'set_iterator'>Setiap tipe iterable mengembalikan tipe iterator khususnya sendiri, tetapi semuanya bekerja dengan cara yang sama—kamu memanggil next() untuk mendapatkan nilai berikutnya.
35.3.2) Fungsi next()
Fungsi next() mengambil item berikutnya dari sebuah iterator. Saat tidak ada item lagi, ia melempar StopIteration:
colors = ["red", "green", "blue"]
iterator = iter(colors)
# Mengambil item satu per satu
print(next(iterator)) # Output: red
print(next(iterator)) # Output: green
print(next(iterator)) # Output: blue
# Tidak ada item lagi
try:
print(next(iterator)) # Raises StopIteration
except StopIteration:
print("No more colors") # Output: No more colors35.3.3) Memberikan Nilai Default pada next()
Kamu bisa memberikan nilai default ke next() sebagai argumen kedua. Saat iterator habis, alih-alih melempar exception StopIteration, next() akan mengembalikan nilai default yang kamu tentukan:
numbers = [1, 2, 3]
iterator = iter(numbers)
print(next(iterator)) # Output: 1
print(next(iterator)) # Output: 2
print(next(iterator)) # Output: 3
print(next(iterator, "Done")) # Output: Done (default value, no exception)
print(next(iterator, "Done")) # Output: Done (still exhausted)Ini berguna saat kamu ingin menangani akhir iterasi secara mulus tanpa penanganan exception:
35.4) Membuat Iterator Kustom dengan iter dan next
35.4.1) Kenapa Membuat Iterator Kustom
Iterable bawaan Python (list, string, file) mencakup sebagian besar kasus umum. Namun, kadang kamu perlu membuat objek iterable-mu sendiri untuk perilaku yang lebih spesifik:
- Menghasilkan urutan dengan logika kustom
- Melakukan iterasi pada struktur data yang kamu desain
- Membuat iterasi yang hemat memori untuk dataset besar
- Menerapkan evaluasi malas (lazy evaluation) (menghitung nilai hanya saat dibutuhkan)
Membuat iterator kustom membutuhkan implementasi dua metode spesial: __iter__() dan __next__().
35.4.2) Protokol Iterator
Agar sebuah objek menjadi iterator, objek tersebut harus mengimplementasikan:
__iter__(): Mengembalikan objek iterator itu sendiri (biasanyaself)__next__(): Mengembalikan nilai berikutnya dalam urutan, atau melemparStopIterationsaat selesai
class SimpleCounter:
"""Sebuah iterator yang menghitung dari start sampai end."""
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
"""Kembalikan objek iterator (self)."""
return self
def __next__(self):
"""Kembalikan nilai berikutnya atau lempar StopIteration."""
if self.current > self.end:
raise StopIteration
value = self.current
self.current += 1
return value
# Menggunakan iterator kustom
counter = SimpleCounter(1, 5)
for num in counter:
print(num)
# Output: 1
# Output: 2
# Output: 3
# Output: 4
# Output: 5Mari kita uraikan apa yang terjadi:
- Loop
formemanggiliter(counter), yang memanggilcounter.__iter__()dan mendapatkan kembalicounteritu sendiri - Loop berulang kali memanggil
next(counter), yang memanggilcounter.__next__() - Setiap pemanggilan
__next__()mengembalikan angka berikutnya dan menambahcurrent - Saat
current > end,__next__()melemparStopIteration, dan loop berhenti
35.4.3) Penggunaan Manual Iterator Kustom
Kamu juga bisa menggunakan iterator kustom secara manual dengan iter() dan next():
counter = SimpleCounter(10, 13)
# Ambil iterator (mengembalikan dirinya sendiri)
iterator = iter(counter)
print(iterator is counter) # Output: True
# Ambil nilai secara manual
print(next(iterator)) # Output: 10
print(next(iterator)) # Output: 11
print(next(iterator)) # Output: 12
print(next(iterator)) # Output: 13
# Sekarang sudah habis
try:
print(next(iterator))
except StopIteration:
print("Counter exhausted") # Output: Counter exhausted35.4.4) Iterator Bisa Habis (Dibahas Ulang)
Ingat bahwa iterator hanya bisa dipakai sekali:
counter = SimpleCounter(1, 3)
# Iterasi pertama
for num in counter:
print(num) # Output: 1, 2, 3
# Iterasi kedua (tidak berfungsi - iterator sudah habis)
for num in counter:
print(num) # Tidak mencetak apa pun - iterator sudah habisUntuk iterasi lagi, kamu perlu membuat instance baru:
# Buat counter baru untuk setiap iterasi
for num in SimpleCounter(1, 3):
print(num) # Output: 1, 2, 3
for num in SimpleCounter(1, 3):
print(num) # Output: 1, 2, 3 (iterator baru)35.4.5) Membuat Kelas Iterable (Bukan Hanya Iterator)
Seringnya, kamu ingin sebuah class yang iterable tetapi membuat iterator baru setiap kali. Untuk melakukan ini, pisahkan iterable dari iterator:
class CounterIterable:
"""Sebuah iterable yang membuat iterator counter baru."""
def __init__(self, start, end):
self.start = start
self.end = end
def __iter__(self):
"""Kembalikan iterator baru setiap kali."""
return CounterIterator(self.start, self.end)
class CounterIterator:
"""Iterator sebenarnya yang melakukan proses menghitung."""
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.current > self.end:
raise StopIteration
value = self.current
self.current += 1
return value
# Sekarang kita bisa melakukan iterasi berkali-kali
counter = CounterIterable(1, 3)
# Iterasi pertama
for num in counter:
print(num) # Output: 1, 2, 3
# Iterasi kedua (berfungsi karena __iter__ membuat iterator baru)
for num in counter:
print(num) # Output: 1, 2, 3Pola ini memisahkan tanggung jawab:
CounterIterableadalah iterable—ia tahu cara membuat iteratorCounterIteratoradalah iterator—ia tahu cara melangkah melalui nilai
35.4.6) Contoh Praktis: Iterasi pada Struktur Data Kustom
Mari buat iterator untuk struktur data kustom—playlist sederhana:
class Playlist:
"""Playlist musik yang bisa diiterasi."""
def __init__(self):
self.songs = []
def add_song(self, title, artist):
"""Menambahkan lagu ke playlist."""
self.songs.append({"title": title, "artist": artist})
def __iter__(self):
"""Kembalikan iterator untuk playlist."""
return PlaylistIterator(self.songs)
class PlaylistIterator:
"""Iterator untuk melangkah melalui lagu-lagu dalam playlist."""
def __init__(self, songs):
self.songs = songs
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.songs):
raise StopIteration
song = self.songs[self.index]
self.index += 1
return song
# Menggunakan playlist
playlist = Playlist()
playlist.add_song("Imagine", "John Lennon")
playlist.add_song("Bohemian Rhapsody", "Queen")
playlist.add_song("Hotel California", "Eagles")
# Iterasi pada lagu
print("Now playing:")
for song in playlist:
print(f" {song['title']} by {song['artist']}")
# Output: Now playing:
# Output: Imagine by John Lennon
# Output: Bohemian Rhapsody by Queen
# Output: Hotel California by Eagles
# Bisa diiterasi lagi (membuat iterator baru)
print("\nReplay:")
for song in playlist:
print(f" {song['title']}")
# Output: Replay:
# Output: Imagine
# Output: Bohemian Rhapsody
# Output: Hotel California35.4.7) Kapan Menggunakan Iterator Kustom
Buat iterator kustom ketika:
- Kamu butuh evaluasi malas (lazy evaluation): Menghasilkan nilai sesuai permintaan alih-alih menyimpan semuanya
- Kamu punya struktur data kustom: Jadikan iterable supaya bisa bekerja dengan loop
for - Kamu butuh logika iterasi khusus: Melewati item, mentransformasi nilai, atau menerapkan langkah yang kompleks
- Efisiensi memori penting: Menghasilkan urutan besar tanpa menyimpannya
Namun, di Bab 36, kamu akan belajar tentang generator (generators), yang menyediakan cara jauh lebih sederhana untuk membuat iterator menggunakan keyword yield. Generator biasanya lebih disukai dibanding mengimplementasikan __iter__() dan __next__() secara manual karena lebih ringkas dan lebih mudah dipahami.
Memahami cara membuat iterator kustom memberimu wawasan tentang bagaimana protokol iterasi Python bekerja, meskipun kamu seringnya akan memakai generator sebagai gantinya. Konsep yang kamu pelajari di sini—__iter__(), __next__(), dan StopIteration—bersifat fundamental untuk memahami generator dan teknik iterasi lanjutan lainnya di bab berikutnya.