检查__iter__
适用于序列类型,但它会失败,例如Python 2 中的字符串。我也想知道正确的答案,在此之前,这里有一种可能性(也适用于字符串):
try:
some_object_iterator = iter(some_object)
except TypeError as te:
print some_object, 'is not iterable'
iter
内置检查__iter__
方法,或者在字符串的情况下检查__getitem__
方法。
另一种通用的 pythonic 方法是假设一个可迭代的,然后如果它不能在给定的对象上工作则优雅地失败。 Python 词汇表:
Pythonic 编程风格通过检查其方法或属性签名而不是通过与某个类型对象的显式关系来确定对象的类型(“如果它看起来像鸭子和嘎嘎叫鸭子 ,它必须是鸭子 。”)通过强调接口而不是特定类型,精心设计的代码通过允许多态替换来提高其灵活性。 Duck-typing 避免使用 type()或 isinstance()进行测试。 相反,它通常采用 EAFP(更容易请求宽恕而非许可)的编程风格。
...
try: _ = (e for e in my_object) except TypeError: print my_object, 'is not iterable'
collections
模块提供了一些抽象基类,如果它们提供特定功能,它们可以询问类或实例,例如:
from collections.abc import Iterable
if isinstance(e, Iterable):
# e is iterable
但是,这不会检查可通过__getitem__
迭代的类。
try:
iterator = iter(theElement)
except TypeError:
# not iterable
else:
# iterable
# for obj in iterator:
# pass
使用抽象基类 。他们至少需要 Python 2.6 并且只适用于新式类。
from collections.abc import Iterable # import directly from collections for Python < 3.3
if isinstance(theElement, Iterable):
# iterable
else:
# not iterable
但是,如文档所述, iter()
更可靠:
检查
isinstance(obj, Iterable)
检测注册为 Iterable 或具有__iter__()
方法的类,但它不检测使用__getitem__()
方法迭代的类。确定对象是否可迭代的唯一可靠方法是调用iter(obj)
。
我想更多地了解iter
, __iter__
和__getitem__
的相互作用以及幕后发生的事情。有了这些知识,你就能理解为什么你能做的最好
try:
iter(maybe_iterable)
print('iteration will probably work')
except TypeError:
print('not iterable')
我将首先列出事实,然后快速提醒您在 python 中使用for
循环时会发生什么,然后进行讨论以说明事实。
如果至少满足下列条件之一,则可以通过调用iter(o)
从任何对象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
循环为了跟进,您需要了解在 Python 中使用for
循环时会发生什么。如果您已经知道,请随意跳到下一部分。
当你for item in o
某个可迭代对象o
使用for item in o
,Python 会调用iter(o)
并期望迭代器对象作为返回值。迭代器是实现__next__
(或 Python 2 中的next
)方法和__iter__
方法的任何对象。
按照惯例,迭代器的__iter__
方法应该返回对象本身(即return self
)。然后 Python 在迭代器上调用next
,直到引发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
使用BasicIterable
实例调用iter
会返回一个没有任何问题的迭代器,因为BasicIterable
实现了__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
,并且在内部处理为item == 3
引发的IndexError
。这就是使用for
循环遍历BasicIterable
按预期工作的原因:
>>> 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
请注意,对__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__
时检查迭代器 :
当为对象o
调用iter(o)
,如果方法存在,则iter
将确保__iter__
的返回值是迭代器。这意味着返回的对象必须实现__next__
(或 Python 2 中的next
)和__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
成功,但会抛出异常的第一次调用__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__
和__getitem__
__iter__
, 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
在__getitem__
足够时实现__iter__
方法。
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
自定义 iterables 应该实现__iter__
的原因如下:
__iter__
,则实例将被视为 iterables,而isinstance(o, collections.Iterable)
将返回True
。 __iter__
返回的对象不是迭代器,则iter
将立即失败并引发TypeError
。 __getitem__
的特殊处理。再次从 Fluent Python 引用: 这就是为什么任何 Python 序列都是可迭代的:它们都实现了
__getitem__
。事实上,标准序列也实现__iter__
,你也应该实现,因为__getitem__
的特殊处理是出于向后兼容的原因而存在,并且将来可能会消失(虽然我写这篇文章时不会弃用)。