Elimde bir obje olsun ve bu objenin yinelenebilir olup olmadığını merak ettiğimi varsayalım. Bunun için en uygun yöntem objenin yinelenebilir olduğunu varsaymak, eğer bu çalışmazsa düzgün bir şekilde başarısız olmaktır:
try:
iter(belki_yinelenebilir)
print('yineleme muhtemelen çalışır')
except TypeError:
print('yinelenemez')
Peki neden böyle? iter, __iter__, __getitem__
fonksiyonlarının etkileşimine ve perdenin arkasında neler olduğuna bakalım. Bu bilgiyle donanarak, yapabileceğiniz en iyi şeyin neden yukarıdaki olduğunu anlayacağız.
Önce elimizdeki gerçekleri listeleyelim. Ardından Python’da bir döngü çalıştırdığınızda ne olduğuna hızlı bir şekilde bakalım, sonrada bu gerçekleri örneklendirelim.
Gerçekler
- Aşağıdaki koşullardan en az biri doğruysa
o
objesi içiniter(o)
ile herhangi bir nesneden yineleme alabilirsiniz:
a)o
nesnesi yineleyici döndüren bir__iter__
metoduna sahiptir. Yineleyici,__iter__
ve__next__
(python 2’denext
) metotlarını içeren herhangi bir nesne olmalı.
b)o
,__getitem__
metoduna sahiptir. - Bir nesnenin
Iterable
veSequence
örneği olması veya__iter__
özniteliğini denetlemek yeterli değildir - Eğer
o
nesnesi sadece__getitem__
metoduna sahipse ve__iter__
yoksa,iter(o)
, 0’dan başlayarak tamsayı dizininden öğeleri getirmeye çalışan bir yineleyici oluşturur. Yineleyici, önceIndexError
hatası verir, sonrasında iseStopIteration
. - En genel anlamda,
iter
ile geri dönen yineleyicinin doğru olduğunu denemek dışında kontrol etmenin bir yolu yoktur. __iter__
kazanır. Bir nesne__iter__
ve__getitem__
metotlarının her ikisine de sahipseiter(o)
,__iter__
‘i çağırı.- Kendi nesnelerinizi yinelemeli hale getirmek istiyorsanız, her zaman
__iter__
metodunu uygulamalısınız.
for
Döngüsü
Devam etmeden önce Python’da bir döngü çalıştırdığınızda ne olacağını anlamanız gerekir.
Bir yenilenebilir o
nesnesi için for item in o
yinelemesi kullanıldığında, Python iter(o)
metodunu çalıştırır ve bir yineleyici nesne dönmesini bekler. Yineleyici, __iter__
ve __next__
(python 2’de next
) metotlarını içeren herhangi bir nesnedir.
Kural gereği, bir yineleyicinin __iter__
yöntemi nesnenin kendisini döndürmelidir (yani return self
). Python daha sonra StopIteration
hatası dönene kadar yineleyicide next
‘i çağırır. Tüm bunlar örtülü olarak gerçekleşir, ancak aşağıdaki örnek bunu görünür hale getirir:
import random
class DemoIterable(object):
def __iter__(self):
print('__iter__ called')
return DemoIterator()
class DemoIterator(object):
def __iter__(self):
return self
def __next__(self):
print('__next__ called')
r = random.randint(1, 10)
if r == 5:
print('raising StopIteration')
raise StopIteration
return r
DemoIterable
üzerinde yineleme:
>>> di = DemoIterable()
>>> for x in di:
... print(x)
...
__iter__ called
__next__ called
9
__next__ called
8
__next__ called
10
__next__ called
3
__next__ called
10
__next__ called
raising StopIteration
Tartışma ve Örnekler
1. ve 2.
Aşağıdaki sınıfı göz önünde bulundurun:
class BasicIterable(object):
def __getitem__(self, item):
if item == 3:
raise IndexError
return item
iter
‘i çağırmak, yinelemeyi BasicIterable
üzerinden sorunsuz bir şekilde döndürür, çünkü BasicIterable __getitem__
metoduna sahip.
>>> b = BasicIterable()
>>> iter(b)
<iterator object at 0x7f1ab216e320>
Ancak b
, __iter__
‘e sahip olmadığından Iterable veya Sequence örneği olarak kabul edilmez.
>>> from collections import Iterable, Sequence
>>> hasattr(b, '__iter__')
False
>>> isinstance(b, Iterable)
False
>>> isinstance(b, Sequence)
False
Bu nedenle, Luciano Ramalho’nun akıcı Python‘unda, bir nesnenin yinelenebilir olup olmadığını kontrol etmenin en doğru yolu olarak o nesne üzerinde yinelenme yapmayı ve işlemeyi önerir.
3. noktada: Yalnızca __getitem__
sağlayan nesneler üzerinde yineleme, __iter__
değil
BasicIterable
örneği üzerinde yineleme beklendiği gibi çalışır: Python, bir IndexError
hatası alana kadar sıfırdan başlayarak dizine göre öğeleri getirmeye çalışan bir yineleme oluşturur. Demo nesnesinin __getitem__
metodu, item
‘i döndürür.
>>> b = BasicIterable()
>>> it = iter(b)
>>> next(it)
0
>>> next(it)
1
>>> next(it)
2
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Yineleyicinin bir sonraki öğeyi döndüremediğinde StopIteration
hatası verir ve IndexError içeride halledilir.
>>> for x in b:
... print(x)
...
0
1
2
4: iter
, __iter__
çağrıldığında yinelemeyi kontrol eder:
iter(o)
ile bir nesne için çağrıldığında, eğer __iter__
yöntemi varsa, dönüş değerinin bir yinelemeci olduğundan emin olmak ister. Bu, döndürülen nesnenin __next__
‘i uygulaması gerektiği anlamına gelir. iter
yalnızca __getitem__
‘i sağlayan nesneler için herhangi bir denetim gerçekleştiremez, çünkü nesnenin öğelerine sayı indexi ile erişilebilir olup olmadığını denetlemenin bir yolunu bulamaz.
class FailIterIterable(object):
def __iter__(self):
return object() # not an iterator
class FailGetitemIterable(object):
def __getitem__(self, item):
raise Exception
FailIterIterable
örneği başlar başlamaz hata verirken, FailGetItemIterable
ancak ilk __next__
çağrısında hata verir.
>>> fii = FailIterIterable()
>>> iter(fii)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: iter() returned non-iterator of type 'object'
>>>
>>> fgi = FailGetitemIterable()
>>> it = iter(fgi)
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/path/iterdemo.py", line 42, in __getitem__
raise Exception
Exception
6. noktada: __iter__
kazandı
Bu çok açık. Bir nesne __iter__
ve __getitem__
‘e sahipse iter
, __iter__
‘i çağırır. Aşağıdaki sınıfı göz önünde bulundurun:
class IterWinsDemo(object):
def __iter__(self):
return iter(['__iter__', 'wins'])
def __getitem__(self, item):
return ['__getitem__', 'wins'][item]
ve bir örnek:
>>> iwd = IterWinsDemo()
>>> for x in iwd:
... print(x)
...
__iter__
wins
7. noktada: yinelemeli sınıflarınız __iter__
‘e sahip olmalı
Kendinize neden list gibi yerleşik bir çok dizinin __iter__’i implement ettiğini sorabilirsiniz. Özellikle __getitem__
yeterli olacakken:
class WrappedList(object): # note: no inheritance from list!
def __init__(self, lst):
self._list = lst
def __getitem__(self, item):
return self._list[item]
Sonuçta, yukarıdaki sınıfın örnekleri üzerinde yineleme __getitem__
‘i çağıracak.
>>> wl = WrappedList(['A', 'B', 'C'])
>>> for x in wl:
... print(x)
...
A
B
C
Özel yinelemelerinizin neden __iter__
‘i uygulanması gerektiğine dair nedenler şunlar:
__iter__
‘i uygularsanız, örnekler yinelenebilir olarak kabul edilir veisinstance(o, collections.abc.Iterable)
True
döner.__iter__
tarafından döndürülen nesne bir yineleyici değilse, hemen başarısız olur ve birTypeError
hatası verir.- __getitem__’in kullanımı geriye dönük uyumluluk sağlar.