我想摆脱一点点的相互作用越轻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
将返回迭代器,而不会出现任何问题,因为BasicIterable
Implements __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__
方法,而Iterable
ABC则不考虑。
关于第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__
也足以使对象变得可迭代