Biraz karşılıklı etkileşimine daha fazla ışık biraz döken istiyorum iter
, __iter__
ve __getitem__
ne perde arkasında olur. Bu bilgiyle donanmış olarak, en iyi neden yapabileceğinizi anlayabileceksiniz.
try:
iter(maybe_iterable)
print('iteration will probably work')
except TypeError:
print('not iterable')
Önce gerçekleri listeleyeceğim ve daha sonra python'da bir for
döngü kullandığınızda neler olduğunu hızlı bir hatırlatma ve ardından gerçekleri göstermek için bir tartışma izleyeceğim.
Gerçekler
Herhangi nesneden bir yineleyici alabilirsiniz o
çağırarak iter(o)
: aşağıdaki koşullardan en az birinin de geçerlidir eğer
) bir o
bir sahip __iter__
bir yineleyici nesnesi döndüren yöntemi. Yineleyici, __iter__
ve __next__
(Python 2:) next
yöntemine sahip herhangi bir nesnedir .
b) o
bir __getitem__
yöntemi vardır.
Iterable
Veya öğesinin örneğini Sequence
denetlemek veya özniteliği denetlemek __iter__
yeterli değildir.
Bir nesne ise o
uygular sadece __getitem__
değil __iter__
, iter(o)
çalışır öğeleri getirmesi olduğunu bir yineleyici inşa edecek o
index 0 dan başlayarak tamsayı endeksine göre yineleyici herhangi yakalayacak IndexError
yükseltilmiş olması (ama başka hatalar) ve daha sonra yükseltir StopIteration
kendisini.
En genel anlamda, geri dönen yineleyicinin iter
denemek dışında aklı başında olup olmadığını kontrol etmenin bir yolu yoktur .
Bir nesne o
uygulanırsa __iter__
, iter
işlev, döndürülen nesnenin __iter__
bir yineleyici olduğundan emin olur . Sadece bir cismin uygulanıp uygulanmadığı konusunda bir akıl sağlığı kontrolü yoktur __getitem__
.
__iter__
kazanır. Bir nesne ise o
uygular hem __iter__
ve __getitem__
, iter(o)
arayacak __iter__
.
Kendi nesnelerinizi yinelenebilir hale getirmek istiyorsanız, her zaman __iter__
yöntemi uygulayın.
for
döngüler
Takip etmek için for
, Python'da bir döngü kullandığınızda ne olduğunu anlamanız gerekir . Zaten biliyorsanız, bir sonraki bölüme geçmekten çekinmeyin.
for item in o
Bazı yinelenebilir nesne için kullandığınızda o
, Python iter(o)
bir yineleyici nesnesini döndürür ve dönüş değeri olarak bekler. Yineleyici, __next__
(veya next
Python 2'de) bir yöntem ve yöntem uygulayan herhangi bir nesnedir __iter__
.
Geleneksel olarak, __iter__
bir yineleyici yöntem nesnenin kendisi (yani döndürmelidir return self
). Python , yükseltilene next
kadar yineleyiciyi çağırır StopIteration
. Bütün bunlar dolaylı olarak gerçekleşir, ancak aşağıdaki gösteri onu görünür kılar:
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
Üzerinde yineleme DemoIterable
:
>>> 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 çizimler
1. ve 2. noktalarda: yineleyici ve güvenilir olmayan kontroller almak
Aşağıdaki sınıfı düşünün:
class BasicIterable(object):
def __getitem__(self, item):
if item == 3:
raise IndexError
return item
Örneğinin çağrılması iter
, uygulayıcılar BasicIterable
nedeniyle sorunsuz bir yineleyici döndürür .BasicIterable
__getitem__
>>> b = BasicIterable()
>>> iter(b)
<iterator object at 0x7f1ab216e320>
Ancak, o notta önemlidir b
yoktur __iter__
özelliğini ve bir örneği olarak kabul edilmez Iterable
ya Sequence
:
>>> from collections import Iterable, Sequence
>>> hasattr(b, '__iter__')
False
>>> isinstance(b, Iterable)
False
>>> isinstance(b, Sequence)
False
Bu nedenle Luciano Ramalho'dan Fluent Python , bir nesnenin yinelenebilir olup olmadığını kontrol etmenin en doğru yol olarak iter
potansiyelin çağrılmasını ve ele alınmasını TypeError
önerir. Doğrudan kitaptan alıntı:
Python x
3.4'ten itibaren, bir nesnenin yinelenebilir olup olmadığını kontrol etmenin en doğru yolu , değilse iter(x)
bir TypeError
istisnayı çağırmak ve işlemektir . Bu, kullanmaktan daha doğrudur isinstance(x, abc.Iterable)
, çünkü ABC yöntemi iter(x)
de eski __getitem__
yöntemi dikkate alır Iterable
.
3. nokta: Yalnızca sağlayan __getitem__
ancak sağlamayan nesneler üzerinde yineleme__iter__
BasicIterable
Beklendiği gibi bir çalışma örneği üzerinde yineleme : Python, bir IndexError
yükseltilene kadar sıfırdan başlayarak öğeleri dizine göre almaya çalışan bir yineleyici oluşturur. Demo nesnesinin __getitem__
yöntemi , döndürülen yineleyici tarafından item
argüman olarak sağlanan yöntemi döndürür .__getitem__(self, item)
iter
>>> 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 StopIteration
sonraki öğeyi döndüremediğinde IndexError
yükseldiğini ve bunun için yükseltildiğini item == 3
dahili olarak ele aldığını unutmayın . Bu nedenle BasicIterable
, bir for
döngü ile döngü oluşturmak beklendiği gibi çalışır:
>>> for x in b:
... print(x)
...
0
1
2
Burada, yineleyicinin döndürdüğü kavramı iter
öğelere dizine göre nasıl erişmeye çalıştığı kavramını yönlendirmek için başka bir örnek . WrappedDict
miras almaz dict
, yani örneklerin bir __iter__
yöntemi olmaz.
class WrappedDict(object): # note: no inheritance from dict!
def __init__(self, dic):
self._dict = dic
def __getitem__(self, item):
try:
return self._dict[item] # delegate to dict.__getitem__
except KeyError:
raise IndexError
Köşeli parantez notasyonunun sadece bir steno olduğu çağrılara __getitem__
delege edildiğini unutmayın dict.__getitem__
.
>>> w = WrappedDict({-1: 'not printed',
... 0: 'hi', 1: 'StackOverflow', 2: '!',
... 4: 'not printed',
... 'x': 'not printed'})
>>> for x in w:
... print(x)
...
hi
StackOverflow
!
4. ve 5. noktalarda: iter
çağırdığında yineleyici olup olmadığını kontrol eder__iter__
:
Ne zaman iter(o)
bir nesne için çağrılır o
, iter
dönüş değeri emin olacaktır __iter__
yöntem, mevcut ise, Yineleyicinin. Bu, döndürülen nesnenin __next__
(veya next
Python 2'de) uygulanması gerektiği anlamına gelir __iter__
. iter
yalnızca sağlayan nesneler için herhangi bir sağlık kontrolü gerçekleştiremez __getitem__
, çünkü nesnenin öğelerine tamsayı dizini tarafından erişilip erişilemediğini kontrol etmenin bir yolu yoktur.
class FailIterIterable(object):
def __iter__(self):
return object() # not an iterator
class FailGetitemIterable(object):
def __getitem__(self, item):
raise Exception
FailIterIterable
Örneklerden bir yineleyici oluşturmanın derhal başarısız olduğunu ve yinelemeden yineleyici oluşturmanın FailGetItemIterable
başarılı olduğunu, ancak ilk çağrıya bir İstisna atacağını unutmayın __next__
.
>>> 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__
kazanır
Bu çok açık. Bir nesne uygular ise __iter__
ve __getitem__
, iter
arayacak __iter__
. Aşağıdaki sınıfı düşünün
class IterWinsDemo(object):
def __iter__(self):
return iter(['__iter__', 'wins'])
def __getitem__(self, item):
return ['__getitem__', 'wins'][item]
ve bir örnek üzerinde döngü oluştururken çıktı:
>>> iwd = IterWinsDemo()
>>> for x in iwd:
... print(x)
...
__iter__
wins
7. nokta: yinelenebilir sınıflarınız __iter__
Kendinize neden list
bir __iter__
yöntem uygulamak gibi çoğu yerleşik dizinin __getitem__
yeterli olacağını kendinize sorabilirsiniz .
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, yineleme delegeler aramalar için, yukarıda sınıfın örneklerini üzerinde __getitem__
etmek list.__getitem__
(köşeli ayraç gösterimi kullanılarak), cezası çalışacaktır:
>>> wl = WrappedList(['A', 'B', 'C'])
>>> for x in wl:
... print(x)
...
A
B
C
Özel tekrarlarınızın uygulanması gereken nedenler __iter__
şunlardır:
- Eğer uygularsanız
__iter__
, örnekler dikkate alınan Iterables olacak ve isinstance(o, collections.abc.Iterable)
dönecektir True
.
- Tarafından döndürülen nesne
__iter__
yineleyici değilse, iter
derhal başarısız olur ve a değerini yükseltir TypeError
.
- Özel kullanım,
__getitem__
geriye dönük uyumluluk nedeniyle vardır. Akıcı Python'dan tekrar alıntı:
Bu nedenle herhangi bir Python dizisi yinelenebilir: hepsi uygular __getitem__
. Aslında, standart diziler de uygulanır __iter__
ve sizinki de olmalıdır, çünkü __getitem__
geriye dönük uyumluluk nedenleriyle özel işlemesi vardır ve gelecekte gidebilir (bunu yazarken reddedilmese de).
__getitem__
bir nesneyi tekrarlanabilir hale getirmek için de yeterli