Python'da IndexError list index out of range
Selamlar, bu yazımda Python'da herkesin er ya da geç tanıştığı o klasik IndexError: list index out of range hatasına yakından bakacağız. Konuyu kuru bir referans gibi anlatmak yerine, gerçekte hangi durumlarda bu hatayı yiyoruz, neden yiyoruz ve ben kendi kodumda nasıl bir savunma hattı kuruyorum, onu paylaşacağım. Hadi başlayalım.
Hata mesajı kısa ama anlamı net: listenin hiç olmayan bir indeksine eriştiniz. Python listeleri sıfırdan başlar, son geçerli indeks len(liste) - 1'dir. Bu kadar. Gel gör ki gerçek hayatta bu sınırı aşmanın o kadar çok yolu var ki, hatayı yemeden ekibe katılan bir Python geliştiricisi tanımıyorum.
Off-by-one, en sevilen klasik
Eminim bu kalıbı görmüşsünüzdür:
items = ['a', 'b', 'c']
for i in range(len(items) + 1):
print(items[i])
+ 1'i nereden geldiği belli değil ama burada. Sonuç: i=3'te patlama. Çözümü çoğu zaman daha basit yazmaktan geçiyor. Indeksle uğraşmak yerine doğrudan elemanlar üzerinde dön:
for item in items:
print(item)
Indeks de lazımsa enumerate var, onu da kullanın:
for i, item in enumerate(items):
print(f'{i}: {item}')
Bana sorarsanız, range(len(...)) gördüğünüz her yerde 'burada gerçekten indekse ihtiyacım var mı?' diye bir durup düşünün. Çoğu zaman cevap hayır.
len() ile sınır kontrolü
Indeks dışarıdan geliyorsa, mesela kullanıcı girdisinden ya da bir API cevabından, körü körüne erişmek riskli. Ben tipik olarak şöyle yazıyorum:
def kullaniciya_goster(items, idx):
if 0 <= idx < len(items):
return items[idx]
return None
İki taraflı kontrol önemli. Negatif indeksler Python'da geçerli (sondan sayar) ama bazen istemediğimiz davranışa yol açar. Mesela items[-1] listenin sonunu verir; kullanıcı -1 girdiğinde bunu siz mi istediniz, yoksa hata mı ayıklamak istediniz? İşin aslı, bu noktada projenin kararı. Ben genelde negatif indeksi 'geçersiz' kabul edip yukarıdaki gibi kapatıyorum.
try/except IndexError
Bazen kontrol etmek ergonomik olmuyor, özellikle de iç içe veri yapılarında. O zaman EAFP (izin yerine af dilemek) tarzına geçiyorum:
def safe_get(seq, idx, default=None):
try:
return seq[idx]
except (IndexError, TypeError):
return default
TypeError'i de yakalıyorum çünkü seq None gelirse oradan da yiyebiliriz. Bu küçük yardımcı, kod tabanlarımda en sık tekrar eden parçalardan biri. Sözlüklerin dict.get(key, default) metodu bu davranışı out-of-the-box veriyor; listelerin böyle bir lüksü yok, biz kendi .get'imizi yazıyoruz.
Slicing patlamaz, bu bir özellik
Liste indeksleme aralığı aşarsa hata fırlatır, ama dilimleme (slicing) sessizce çalışır:
items = [1, 2, 3]
print(items[0:10]) # [1, 2, 3]
print(items[10:20]) # []
Bu çok işe yarar. 'İlk 5 elemanı al' tarzı bir şey istiyorsanız items[:5] yazmak hem güvenli hem de sade. Eleman sayısı 5'in altındaysa neyi varsa onu döner, IndexError yemezsiniz.
Paralel listelerde zip alışkanlığı
İki listeyi indeksleyerek paralel gezmek de hata kaynağıdır:
names = ['Alice', 'Bob']
ages = [25, 30, 35]
for i in range(len(ages)):
print(f'{names[i]} is {ages[i]}')
names[2] yok, patlar. Bunun yerine zip daima daha temiz. En kısasında durur, kafa yormazsınız:
for name, age in zip(names, ages):
print(f'{name} is {age}')
Eksik tarafı doldurmak istiyorsanız itertools.zip_longest(fillvalue=...) var.
Sık karşılaşılan tuzaklar
- Iterasyon sırasında listeyi değiştirmek:
for i in range(len(numbers))içindenumbers.pop(i)yapmayın. Indeksler kayar, eninde sonunda IndexError yersiniz. Yeni liste üretin ([n for n in numbers if ...]) ya da geriye doğru gezin. - Boş listeyi kontrol etmemek:
lst[0]yazmadan önceif lst:kontrolü maliyetsiz. API'lerden gelen verilerde boş liste çok olağan bir durum. - Iç içe listelerde sabit indeks:
matrix[r][2]yazıyorsanız, her satırda en az 3 eleman olduğundan emin misiniz? CSV gibi kaynaklar düzensiz olabilir.
Kapanış
Şahsi kanaatim, IndexError'i çözmenin en iyi yolu onu hiç yememektir; yani indekslerle değil koleksiyonlarla düşünmek. for item in items, zip, dilimleme ve küçük bir safe_get çoğu zaman tüm derdi kapatır. Kalan yerlerde de try/except IndexError ya da açık bir 0 <= idx < len(items) kontrolü işi bitirir. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.
