我想摆脱一点点的相互作用越轻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__and __getitem__,iter(o)将调用__iter__。
如果要使自己的对象可迭代,请始终实现该__iter__方法。
for 循环
为了继续学习,您需要了解for在Python中使用循环时会发生什么。如果您已经知道,请随时跳到下一部分。
当for item in o用于某些可迭代对象时o,Python调用iter(o)并期望将迭代器对象作为返回值。迭代器是实现__next__(或next在Python 2中)方法和__iter__方法的任何对象。
按照约定,__iter__迭代器的方法应返回对象本身(即return self)。然后next,Python调用迭代器,直到StopIteration引发为止。所有这些操作都是隐式发生的,但是以下演示使其可见:
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:
>>> 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
讨论与插图
在第1点和第2点:获取迭代器和不可靠的检查
考虑以下类别:
class BasicIterable(object):
def __getitem__(self, item):
if item == 3:
raise IndexError
return item
调用iter的实例BasicIterable将返回迭代器,而不会出现任何问题,因为BasicIterableImplements __getitem__。
>>> b = BasicIterable()
>>> iter(b)
<iterator object at 0x7f1ab216e320>
但是,请务必注意,b该__iter__属性不具有,也不被视为Iterable或的实例Sequence:
>>> from collections import Iterable, Sequence
>>> hasattr(b, '__iter__')
False
>>> isinstance(b, Iterable)
False
>>> isinstance(b, Sequence)
False
这就是为什么Luciano Ramalho的Fluent Python建议调用iter和处理潜能TypeError作为检查对象是否可迭代的最准确方法。直接从书中引用:
从Python 3.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 "<stdin>", line 1, in <module>
StopIteration
请注意,当迭代器StopIteration无法返回下一项时,它将引发该迭代器IndexError,并且为其item == 3内部处理。这就是为什么按预期BasicIterable进行for循环的原因:
>>> for x in b:
... print(x)
...
0
1
2
这是另一个例子,目的是让迭代器返回的迭代器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
注意,将to __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)
...
hi
StackOverflow
!
关于第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 iterator
class FailGetitemIterable(object):
def __getitem__(self, item):
raise Exception
请注意,从FailIterIterable实例构造一个迭代器会立即失败,而从一个实例构造一个迭代器会立即失败FailGetItemIterable,但会在第一次调用时引发Exception __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点:__iter__获胜
这很简单。如果一个对象实现__iter__and __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.abc.Iterable)并将返回True。
- 如果返回的对象
__iter__不是迭代器,iter则将立即失败并引发TypeError。
- 由于
__getitem__向后兼容的原因,存在的特殊处理。再次引用Fluent Python:
这就是任何Python序列都是可迭代的原因:它们都实现了__getitem__。实际上,标准序列也可以实现__iter__,您也应该实现,因为__getitem__出于向后兼容的原因而存在对的特殊处理,并且可能在将来消失(尽管在我撰写本文时不推荐使用)。
__getitem__也足以使对象变得可迭代