<p>我想对<code>iter</code>、<code>__iter__</code>和<code>__getitem__</code>之间的相互作用,以及窗帘后面发生的事情,多透露一点。有了这些知识,你就能明白为什么你能做的最好</p>
<pre><code>try:
iter(maybe_iterable)
print('iteration will probably work')
except TypeError:
print('not iterable')
</code></pre>
<p>我将首先列出事实,然后快速提醒您在python中使用<code>for</code>循环时会发生什么,然后进行讨论以说明事实。</p>
<h2>事实</h2>
<ol>
<li><p>如果以下条件中至少有一个成立,则可以通过调用<code>iter(o)</code>从任何对象<code>o</code>获取迭代器:<br/><br/><a)<code>o</code>具有返回迭代器对象的<code>__iter__</code>方法。迭代器是具有<code>__iter__</code>和<code>__next__</code>(Python 2:<code>next</code>)方法的任何对象。<br/><br/>b)<code>o</code>有一个<code>__getitem__</code>方法。</p></li>
<li><p>检查<code>Iterable</code>或<code>Sequence</code>的实例,或检查
属性<code>__iter__</code>不够。</p></li>
<li><p>如果对象<code>o</code>只实现<code>__getitem__</code>,而不实现<code>__iter__</code>,则<code>iter(o)</code>将构造
试图通过整数索引从<code>o</code>获取项的迭代器,从索引0开始。迭代器将捕获任何引发的<code>IndexError</code>(但没有其他错误),然后引发<code>StopIteration</code>本身。</p></li>
<li><p>在最一般的意义上,除了尝试之外,没有办法检查由<code>iter</code>返回的迭代器是否正常。</p></li>
<li><p>如果对象<code>o</code>实现<code>__iter__</code>,则<code>iter</code>函数将确保
<code>__iter__</code>返回的对象是迭代器。没有健康检查
如果一个对象只实现<code>__getitem__</code>。</p></li>
<li><p><code>__iter__</code>获胜。如果对象<code>o</code>同时实现<code>__iter__</code>和<code>__getitem__</code>,则<code>iter(o)</code>将调用<code>__iter__</code>。</p></li>
<li><p>如果您想使您自己的对象可iterable,请始终从<a href="https://docs.python.org/3/library/collections.abc.html" rel="noreferrer">abstract base class</a><code>Iterable</code>或其子类之一继承。您必须实现<code>__iter__</code>方法,否则将像<code>Sequence</code>那样提供它。</p></li>
</ol>
<h2><code>for</code>环</h2>
<p>为了继续,您需要了解在Python中使用<code>for</code>循环时会发生什么。如果你已经知道了,可以直接跳到下一节。</p>
<p>当对某个iterable对象使用<code>for item in o</code>时,Python将调用<code>iter(o)</code>,并期望返回一个迭代器对象。迭代器是实现Python 2中的<code>__next__</code>(或<code>next</code>)方法和<code>__iter__</code>方法的任何对象。</p>
<p>按照惯例,迭代器的<code>__iter__</code>方法应该返回对象本身(即<code>return self</code>)。然后,Python在迭代器上调用<code>next</code>,直到引发<code>StopIteration</code>。所有这些都是隐式发生的,但以下演示使其可见:</p>
<pre><code>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
</code></pre>
<p>在<code>DemoIterable</code>上迭代:</p>
<pre><code>>>> 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
</code></pre>
<h2>讨论和说明</h2>
<p><strong>关于第1点和第2点:获取迭代器和不可靠的检查</strong></p>
<p>请考虑以下类别:</p>
<pre><code>class BasicIterable(object):
def __getitem__(self, item):
if item == 3:
raise IndexError
return item
</code></pre>
<p>使用实例<code>BasicIterable</code>调用<code>iter</code>将返回一个迭代器,不会有任何问题,因为<code>BasicIterable</code>实现了<code>__getitem__</code>。</p>
<pre><code>>>> b = BasicIterable()
>>> iter(b)
<iterator object at 0x7f1ab216e320>
</code></pre>
<p>但是,需要注意的是,<code>b</code>没有<code>__iter__</code>属性,并且不被视为<code>Iterable</code>或<code>Sequence</code>的实例:</p>
<pre><code>>>> from collections import Iterable, Sequence
>>> hasattr(b, '__iter__')
False
>>> isinstance(b, Iterable)
False
>>> isinstance(b, Sequence)
False
</code></pre>
<p>这就是为什么Luciano Ramalho的<a href="http://shop.oreilly.com/product/0636920032519.do" rel="noreferrer">Fluent Python</a>建议调用<code>iter</code>并处理潜在的<code>TypeError</code>作为检查对象是否可iterable的最精确方法。直接从书中引用:</p>
<blockquote>
<p>As of Python 3.4, the most accurate way to check whether an object <code>x</code> is iterable is to call <code>iter(x)</code> and handle a <code>TypeError</code> exception if it isn’t. This is more accurate than using <code>isinstance(x, abc.Iterable)</code> , because <code>iter(x)</code> also considers the legacy <code>__getitem__</code> method, while the <code>Iterable</code> ABC does not.</p>
</blockquote>
<p><strong>在第3点:迭代仅提供<code>__getitem__</code>,但不提供<code>__iter__</code>的对象</strong></p>
<p>对<code>BasicIterable</code>的实例进行迭代可以按预期工作:Python
构造一个迭代器,该迭代器尝试从零开始按索引获取项,直到引发<code>IndexError</code>。demo对象的<code>__getitem__</code>方法只返回由<code>iter</code>返回的迭代器作为<code>__getitem__(self, item)</code>的参数提供的<code>item</code>。</p>
<pre><code>>>> 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
</code></pre>
<p>注意,迭代器在不能返回下一个i时引发<code>StopIteration</code>以及为<code>item == 3</code>引发的<code>IndexError</code>在内部处理。这就是为什么用^{<cd4>循环遍历<code>BasicIterable</code>的原因:</p>
<pre><code>>>> for x in b:
... print(x)
...
0
1
2
</code></pre>
<p>这里是另一个例子,目的是让我们了解<code>iter</code>返回的迭代器如何通过索引访问项。<code>WrappedDict</code>不是从<code>dict</code>继承的,这意味着实例没有<code>__iter__</code>方法。</p>
<pre><code>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
</code></pre>
<p>注意,对<code>__getitem__</code>的调用被委托给<code>dict.__getitem__</code>,而方括号表示法只是一种简写。</p>
<pre><code>>>> w = WrappedDict({-1: 'not printed',
... 0: 'hi', 1: 'StackOverflow', 2: '!',
... 4: 'not printed',
... 'x': 'not printed'})
>>> for x in w:
... print(x)
...
hi
StackOverflow
!
</code></pre>
<p><strong>在第4点和第5点:<code>iter</code>当迭代器调用<code>__iter__</code>时检查迭代器:</p>
<p>当为对象<code>o</code>调用<code>iter(o)</code>时,<code>iter</code>将确保<code>__iter__</code>的返回值(如果方法存在)是迭代器。这意味着返回的对象
必须实现<code>__next__</code>(或Python 2中的<code>next</code>)和<code>__iter__</code>。<code>iter</code>无法对仅
提供<code>__getitem__</code>,因为它无法检查对象的项是否可以通过整数索引访问。</p>
<pre><code>class FailIterIterable(object):
def __iter__(self):
return object() # not an iterator
class FailGetitemIterable(object):
def __getitem__(self, item):
raise Exception
</code></pre>
<p>请注意,从<code>FailIterIterable</code>实例构造迭代器会立即失败,而从<code>FailGetItemIterable</code>实例构造迭代器会成功,但会在第一次调用<code>__next__</code>时引发异常。</p>
<pre><code>>>> 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
</code></pre>
<p><strong>在第6点:<code>__iter__</code>获胜</p>
<p>这个很简单。如果对象实现<code>__iter__</code>和<code>__getitem__</code>,则<code>iter</code>将调用<code>__iter__</code>。考虑下一个类</p>
<pre><code>class IterWinsDemo(object):
def __iter__(self):
return iter(['__iter__', 'wins'])
def __getitem__(self, item):
return ['__getitem__', 'wins'][item]
</code></pre>
<p>以及在实例上循环时的输出:</p>
<pre><code>>>> iwd = IterWinsDemo()
>>> for x in iwd:
... print(x)
...
__iter__
wins
</code></pre>
<p><strong>关于第7点:iterable类应该实现<code>__iter__</code></strong></p>
<p>您可能会问自己,为什么像<code>list</code>这样的大多数内置序列在<code>__getitem__</code>足够时实现<code>__iter__</code>方法。</p>
<pre><code>class WrappedList(object): # note: no inheritance from list!
def __init__(self, lst):
self._list = lst
def __getitem__(self, item):
return self._list[item]
</code></pre>
<p>毕竟,对上述类的实例进行迭代,将调用委托给<code>__getitem__</code>到<code>list.__getitem__</code>(使用方括号表示法),可以很好地工作:</p>
<pre><code>>>> wl = WrappedList(['A', 'B', 'C'])
>>> for x in wl:
... print(x)
...
A
B
C
</code></pre>
<p>自定义iterable应该实现<code>__iter__</code>的原因如下:</p>
<ol>
<li>如果实现<code>__iter__</code>,实例将被视为可访问的,并且<code>isinstance(o, collections.abc.Iterable)</code>将返回<code>True</code>。</li>
<li>如果<code>__iter__</code>返回的对象不是迭代器,<code>iter</code>将立即失败并引发一个<code>TypeError</code>。</li>
<li>由于向后兼容的原因,存在对<code>__getitem__</code>的特殊处理。再次引用Fluent Python:</li>
</ol>
<blockquote>
<p>That is why any Python sequence is iterable: they all implement <code>__getitem__</code> . In fact,
the standard sequences also implement <code>__iter__</code>, and yours should too, because the
special handling of <code>__getitem__</code> exists for backward compatibility reasons and may be
gone in the future (although it is not deprecated as I write this).</p>
</blockquote>