我想对…的相互作用有一点了解。iter, __iter__和__getitem__窗帘后面发生了什么。有了这些知识,你就能理解为什么你能做的最好的就是try:
iter(maybe_iterable)
print('iteration will probably work')except TypeError:
print('not iterable')
我将首先列出事实,然后快速提醒您使用for循环在python中,然后进行讨论以说明事实。
事实您可以从任何对象获得迭代器。o打电话iter(o)如果至少有下列条件之一有效:
a)o有一个__iter__方法,该方法返回迭代器对象。迭代器是具有__iter__和一个__next__(Python 2:next)方法。
b)o有一个__getitem__方法。
的实例进行检查。Iterable或Sequence,或检查属性。__iter__还不够。
如果一个对象o只实现__getitem__,但不是__iter__, iter(o)将构造一个迭代器,试图从o按整数索引,从索引0开始。迭代器将捕获任何IndexError(但没有其他错误)引发,然后引发StopIteration本身。
在最一般的意义上,无法检查迭代器是否返回iter是理智的,而不是去尝试。
如果一个对象o实施器__iter__,iter函数将确保由__iter__是迭代器。如果对象仅实现,则不进行是否正确的检查。__getitem__.
__iter__赢了。如果一个对象o实现两者__iter__和__getitem__, iter(o)会打电话__iter__.
如果希望使自己的对象可迭代,请始终实现__iter__方法。
for环
为了跟进,您需要了解使用for在Python中循环。如果你已经知道了,可以跳到下一节。
当你使用for item in o对于一些可迭代对象o,Python调用iter(o)并期望迭代器对象作为返回值。迭代器是实现__next__(或next方法和__iter__方法。
根据惯例,__iter__迭代器的方法应该返回对象本身(即return self)。Python然后调用next在迭代器上直到StopIteration已经长大了。所有这些都是隐式的,但下面的演示使其可见:import randomclass 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:>>> di = DemoIterable()>>> for x in di:... print(x)...__iter__ called
__next__ called9__next__ called8__next__ called10__next__ called3__next__ called10__next__ called
raising StopIteration
讨论和说明
关于第1点和第2点:获得迭代器和不可靠的检查
考虑以下课程:class BasicIterable(object):
def __getitem__(self, item):
if item == 3:
raise IndexError
return item
呼叫iter的实例BasicIterable将返回迭代器,而不会出现任何问题,因为BasicIterable实施器__getitem__.>>> b = BasicIterable()>>> iter(b)
然而,重要的是要注意b没有__iter__属性,并且不被视为Iterable或Sequence:>>> from collections import Iterable, Sequence>>> hasattr(b, '__iter__')False>>> isinstance(b, Iterable)False>>> isinstance(b, Sequence)False
这就是为什么流利Python卢西亚诺·拉马略建议iter和处理潜在的TypeError作为检查对象是否可迭代的最准确的方法。直接引用这本书:在Python3.4中,检查对象是否是最准确的方法x是可迭代的,是调用iter(x)处理一个TypeError异常。这比使用isinstance(x, abc.Iterable),因为iter(x)也会考虑遗产__getitem__方法,而IterableABC不知道。
关于第3点:迭代仅提供__getitem__,但不是__iter__
的实例进行迭代。BasicIterable按预期工作:Python构造一个迭代器,该迭代器尝试按索引获取项,从零开始,直到IndexError已经长大了。演示对象的__getitem__方法只返回item作为参数提供给__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 "", line 1, in StopIteration
注意,迭代器引发StopIteration如果它不能返回下一项,并且IndexError为item == 3内部处理。这就是为什么在BasicIterable带着for循环按预期工作:>>> for x in b:... print(x)...012
下面是另一个示例,以了解迭代器是如何由iter尝试按索引访问项。WrappedDict不继承dict,这意味着实例将不具有__iter__方法。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
注意,调用__getitem__委托给dict.__getitem__它的方括号符号只是一个简单的速记。>>> w = WrappedDict({-1: 'not printed',... 0: 'hi', 1: 'StackOverflow', 2: '!',...
4: 'not printed', ... 'x': 'not printed'})>>> for x in w:... print(x)... hiStackOverflow!
关于第4和第5点:iter当迭代器调用__iter__:
什么时候iter(o)为对象调用o, iter的返回值__iter__,如果存在该方法,则为迭代器。这意味着返回的对象必须实现__next__(或next(在Python 2中)和__iter__. iter无法对仅提供__getitem__,因为它无法检查对象的项是否可以通过整数索引访问。class FailIterIterable(object):
def __iter__(self):
return object() # not an iteratorclass FailGetitemIterable(object):
def __getitem__(self, item):
raise Exception
注意,从FailIterIterable实例的迭代器时,实例会立即失败。FailGetItemIterable成功,但将在第一次调用__next__.>>> fii = FailIterIterable()>>> iter(fii)Traceback (most recent call last):
File "", line 1, in TypeError: iter() returned non-iterator of type 'object'>>>>>> fgi = FailGetitemIterable()
>>> it = iter(fgi)>>> next(it)Traceback (most recent call last):
File "", line 1, in
File "/path/iterdemo.py", line 42, in __getitem__ raise ExceptionException
关于第6点:__iter__赢
这个很简单。如果对象实现__iter__和__getitem__, iter会打电话__iter__..考虑下一节课class IterWinsDemo(object):
def __iter__(self):
return iter(['__iter__', 'wins'])
def __getitem__(self, item):
return ['__getitem__', 'wins'][item]
以及遍历实例时的输出:>>> iwd = IterWinsDemo()>>> for x in iwd:... print(x)...__iter__
wins
关于第7点:您的可迭代类应该实现__iter__
你可能会问自己,为什么大多数内置序列像list实现__iter__方法时__getitem__就足够了。class WrappedList(object): # note: no inheritance from list!
def __init__(self, lst):
self._list = lst def __getitem__(self, item):
return self._list[item]
毕竟,对上面类的实例进行迭代,它将调用委托给__getitem__到list.__getitem__(使用方括号符号),工作正常:>>> wl = WrappedList(['A', 'B', 'C'])>>> for x in wl:... print(x)... A
B
C
您的自定义迭代应该实现的原因__iter__如下:如果你实现
__iter__,实例将被视为可迭代的,并且
isinstance(o, collections.Iterable)会回来
True.
返回的对象
__iter__不是迭代器,
iter将立即失败并引发
TypeError.
特殊处理
__getitem__由于向后兼容的原因而存在。再次引用流利的Python:这就是为什么任何Python序列都是可迭代的:它们都实现了__getitem__..实际上,标准序列还实现了__iter__,而你的也应该如此,因为对…的特殊处理。__getitem__因为向后兼容性的原因而存在,而且将来可能会消失(尽管在我写这个时,它并不是不推荐的)。