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
讨论和插图
On point 1 and 2: getting an iterator and unreliable checks
考虑以下课程:
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 的实例:
>>> w = WrappedDict({-1: 'not printed',
... 0: 'hi', 1: 'StackOverflow', 2: '!',
... 4: 'not printed',
... 'x': 'not printed'})
>>> for x in w:
... print(x)
...
hi
StackOverflow
!
On point 4 and 5: iter checks for an iterator when it calls 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
"ask for forgiveness, not permission"的Python哲学没有't work well when e.g. in a list you have both iterables and non-iterables and you need to treat each element differently according to it'类型(在try和非迭代上处理迭代,除了可行,但它看起来很丑陋和误导)
这个问题的解决方案试图实际迭代对象(例如[x for obj])来检查它是否可迭代可能会对大型迭代产生显着的性能损失(特别是如果你只需要迭代的前几个元素,例如)并且应该避免
4
try:
#treat object as iterable
except TypeError, e:
#object is not actually iterable
不要运行检查,看看你的鸭子是否真的是一只鸭子,看它是否可以迭代,把它看作是不是,如果不是就抱怨 .
4
鸭子打字
try:
iterator = iter(theElement)
except TypeError:
# not iterable
else:
# iterable
# for obj in iterator:
# pass
from collections.abc import Iterable # import directly from collections for Python < 3.3
if isinstance(theElement, Iterable):
# iterable
else:
# not iterable
fruits = ("apple", "banana", "peach")
isiterable(fruits) # returns True
num = 345
isiterable(num) # returns False
isiterable(str) # returns False because str type is type class and it's not iterable.
hello = "hello dude !"
isiterable(hello) # returns True because as you know string objects are iterable
4
def is_iterable(x):
try:
0 in x
except TypeError:
return False
else:
return True
这将对所有可迭代对象都说是,但它将 say no to strings in Python 2 . (这就是我想要的例子,当一个递归函数可以带一个字符串或一个字符串容器 . 在这种情况下,asking forgiveness可能会导致obfuscode,最好首先询问权限 . )
import numpy
class Yes:
def __iter__(self):
yield 1;
yield 2;
yield 3;
class No:
pass
class Nope:
def __iter__(self):
return 'nonsense'
assert is_iterable(Yes())
assert is_iterable(range(3))
assert is_iterable((1,2,3)) # tuple
assert is_iterable([1,2,3]) # list
assert is_iterable({1,2,3}) # set
assert is_iterable({1:'one', 2:'two', 3:'three'}) # dictionary
assert is_iterable(numpy.array([1,2,3]))
assert is_iterable(bytearray("not really a string", 'utf-8'))
assert not is_iterable(No())
assert not is_iterable(Nope())
assert not is_iterable("string")
assert not is_iterable(42)
assert not is_iterable(True)
assert not is_iterable(None)
from collections import Iterable
from traceback import print_exc
def check_and_raise(x):
if not isinstance(x, Iterable):
raise TypeError, "%s is not iterable" % x
else:
for i in x:
print i
def just_iter(x):
for i in x:
print i
class NotIterable(object):
pass
if __name__ == "__main__":
try:
check_and_raise(5)
except:
print_exc()
print
try:
just_iter(5)
except:
print_exc()
print
try:
Iterable.register(NotIterable)
ni = NotIterable()
check_and_raise(ni)
except:
print_exc()
print
19 回答
我想更多地了解
iter
,__iter__
和__getitem__
之间的相互作用以及幕后发生的事情 . 有了这些知识,你就能理解为什么你能做的最好我将首先列出事实,然后快速提醒您在python中使用
for
循环时会发生什么,然后进行讨论以说明事实 .事实
iter(o)
从任何对象o
获取迭代器:a)
o
有一个__iter__
方法,它返回一个迭代器对象 . 迭代器是具有__iter__
和__next__
(Python 2:next
)方法的任何对象 .b)
o
有一个__getitem__
方法 .检查
Iterable
或Sequence
的实例,或检查属性__iter__
是不够的 .如果一个对象
o
只实现__getitem__
,而不是__iter__
,iter(o)
将构造一个迭代器,它试图从索引0开始按整数索引从o
中获取项目 . 迭代器将捕获任何IndexError
(但没有其他错误)引发的然后提出StopIteration
本身 .从最普遍的意义上讲,没有办法检查
iter
返回的迭代器是否理智,而不是尝试它 .如果对象
o
实现__iter__
,iter
函数将确保__iter__
返回的对象是迭代器 . 如果对象仅实现__getitem__
,则不进行健全性检查 .__iter__
胜利 . 如果对象o
同时实现__iter__
和__getitem__
,iter(o)
将调用__iter__
.如果要使自己的对象可迭代,请始终实现
__iter__
方法 .for循环
为了跟进,您需要了解在Python中使用
for
循环时会发生什么 . 如果您已经知道,请随意跳到下一部分 .当您对某些可迭代对象
o
使用_88660时,Python调用iter(o)
并期望迭代器对象作为返回值 . 迭代器是实现__next__
(或Python 2中的next
)方法和__iter__
方法的任何对象 .按照惯例,迭代器的
__iter__
方法应该返回对象本身(即return self
) . 然后Python在迭代器上调用next
,直到StopIteration
被引发 . 所有这些都是隐式发生的,但以下演示使其可见:迭代
DemoIterable
:讨论和插图
On point 1 and 2: getting an iterator and unreliable checks
考虑以下课程:
使用
BasicIterable
实例调用iter
将返回迭代器而没有任何问题,因为BasicIterable
实现了__getitem__
.但是,重要的是要注意
b
没有__iter__
属性,不被视为Iterable
或Sequence
的实例:这就是为什么Luciano Ramalho的Fluent Python建议调用
iter
并将潜在的TypeError
作为检查对象是否可迭代的最准确方法 . 直接从书中引用:On point 3: Iterating over objects which only provide getitem, but not iter
迭代
BasicIterable
的实例按预期工作:Python构造一个迭代器,它尝试按索引获取项目,从零开始,直到IndexError
被引发 . 演示对象的__getitem__
方法只返回由iter
返回的迭代器作为__getitem__(self, item)
参数提供的item
.请注意,迭代器在无法返回下一个项目时会引发
StopIteration
,并且在内部处理为item == 3
引发的IndexError
. 这就是为什么循环BasicIterable
与for
循环按预期工作的原因:这是另一个例子,以便将
iter
返回的迭代器如何尝试按索引访问项目的概念 .WrappedDict
不从dict
继承,这意味着实例将没有__iter__
方法 .请注意,对
__getitem__
的调用被委托给dict.__getitem__
,方括号表示法只是简写 .On point 4 and 5: iter checks for an iterator when it calls iter :
当为对象
o
调用iter(o)
时,iter
将确保__iter__
的返回值(如果存在该方法)是迭代器 . 这意味着返回的对象必须实现__next__
(或Python 2中的next
)和__iter__
.iter
无法对仅提供__getitem__
的对象执行任何健全性检查,因为它无法检查对象的项是否可通过整数访问指数 .请注意,从
FailIterIterable
实例构造迭代器会立即失败,而从FailGetItemIterable
构造迭代器会成功,但会在第一次调用__next__
时抛出异常 .On point 6: iter wins
这个很简单 . 如果一个对象实现
__iter__
和__getitem__
,iter
将调用__iter__
. 考虑以下课程循环实例时的输出:
On point 7: your iterable classes should implement iter
您可能会问自己为什么大多数内置序列(如
list
)在__getitem__
足够时实现__iter__
方法 .毕竟,迭代上面的类的实例,将
__getitem__
的调用委托给list.__getitem__
(使用方括号表示法),可以正常工作:您的自定义iterables应该实现
__iter__
的原因如下:如果实现
__iter__
,则实例将被视为可迭代,isinstance(o, collections.Iterable)
将返回True
.如果
__iter__
返回的对象不是迭代器,iter
将立即失败并引发TypeError
.出于向后兼容性原因,存在
__getitem__
的特殊处理 . 再次从Fluent Python引用:到目前为止我找到的最佳解决方案:
hasattr(obj, '__contains__')
它基本上检查对象是否实现了
in
运算符 .Advantages (其他解决方案都没有全部三个):
它是一个表达式(作为 lambda ,而不是 try...except 变体)
它应该(应该)由所有迭代实现,包括 strings (而不是
__iter__
)适用于任何Python> = 2.5
笔记:
"ask for forgiveness, not permission"的Python哲学没有't work well when e.g. in a list you have both iterables and non-iterables and you need to treat each element differently according to it'类型(在try和非迭代上处理迭代,除了可行,但它看起来很丑陋和误导)
这个问题的解决方案试图实际迭代对象(例如[x for obj])来检查它是否可迭代可能会对大型迭代产生显着的性能损失(特别是如果你只需要迭代的前几个元素,例如)并且应该避免
不要运行检查,看看你的鸭子是否真的是一只鸭子,看它是否可以迭代,把它看作是不是,如果不是就抱怨 .
鸭子打字
类型检查
使用Abstract Base Classes . 他们至少需要Python 2.6并且只适用于新式类 .
但是,如by the documentation所述_84620_更可靠:
pandas有这样的内置函数:
__iter__
适用于序列类型,但它会失败,例如字符串 in Python 2 . 我也想知道正确的答案,在此之前,这里有一种可能性(也适用于字符串):iter
内置检查__iter__
方法,或者在字符串的情况下检查__getitem__
方法 .但是,这不会检查可通过
__getitem__
迭代的类 .你可以试试这个:
如果我们可以生成一个迭代它的生成器(但从不使用生成器,因此它不占用空间),它是可迭代的 . 看起来像是一种“呃”的东西 . 为什么需要首先确定变量是否可迭代?
我找到了一个很好的解决方案here:
如果object是可迭代的,则以下代码中的
isiterable
func返回True
. 如果它不可迭代返回False
例
这将对所有可迭代对象都说是,但它将 say no to strings in Python 2 . (这就是我想要的例子,当一个递归函数可以带一个字符串或一个字符串容器 . 在这种情况下,asking forgiveness可能会导致obfuscode,最好首先询问权限 . )
这里的许多其他策略都会对字符串说“是” . 如果这是你想要的,请使用它们 .
注意:is_iterable()会对
bytes
和bytearray
类型的字符串说“是” .Python 3中的
bytes
对象是可迭代的True == is_iterable(b"string") == is_iterable("string".encode('utf-8'))
Python 2中没有这样的类型 .Python 2和3中的
bytearray
对象是可迭代的True == is_iterable(bytearray(b"abc"))
O.P.
hasattr(x, '__iter__')
方法会对Python 3中的字符串说“是”而对Python 2中的“否”(无论是''
还是b''
或u''
) . 感谢@LuisMasuelli注意到它也会让你失望__iter__
.从 Python 3.5 开始,您可以使用标准库中的typing模块来处理类型相关的事情:
这还不够:
__iter__
返回的对象必须实现迭代协议(即next
方法) . 请参阅documentation中的相关部分 .在Python中,一个好的做法是“尝试看”而不是“检查” .
在Python <= 2.5中,你不能也不应该 - iterable是一个“非正式”的接口 .
但是,从Python 2.6和3.0开始,您可以利用新的ABC(抽象基类)基础结构以及集合模块中提供的一些内置ABC:
现在,无论这是可取的还是实际的,只是一个惯例问题 . 如您所见,您可以将非可迭代对象注册为Iterable - 它将在运行时引发异常 . 因此,isinstance获得了一个"new"含义 - 它只是检查"declared"类型兼容性,这是一个很好的Python方法 .
另一方面,如果您的对象不满足您需要的界面,您打算做什么?采用以下示例:
如果对象不满足您的期望,您只需抛出一个TypeError,但如果已经注册了正确的ABC,则您的检查无效 . 相反,如果
__iter__
方法可用,Python将自动将该类的对象识别为Iterable .所以,如果你只是期望一个迭代,迭代它并忘记它 . 另一方面,如果您需要根据输入类型执行不同的操作,您可能会发现ABC基础结构非常有用 .
我总是不知道为什么python有
callable(obj) -> bool
而不是iterable(obj) -> bool
......肯定它更容易做
hasattr(obj,'__call__')
即使它更慢 .由于几乎所有其他答案都建议使用
try
/except TypeError
,其中对异常的测试通常被认为是任何语言中的不良做法,这里是iterable(obj) -> bool
的实现,我越来越喜欢并经常使用:对于python 2 's sake, I' ll使用lambda只是为了额外的性能提升......
(在python 3中,用于定义函数的内容并不重要,
def
与lambda
的速度大致相同)请注意,对于具有
__iter__
的对象,此函数执行速度更快,因为它不会测试__getitem__
.大多数可迭代对象应该依赖于
__iter__
,其中特殊情况对象回退到__getitem__
,尽管对象是可迭代的 .(因为这是标准的,它也会影响C对象)
根据Python 2 Glossary,迭代是
当然,考虑到Python的一般编码风格,它基于“更容易请求宽恕而不是许可”这一事实,一般的期望是使用
但是如果你需要明确地检查它,你可以通过
hasattr(object_in_question, "__iter__") or hasattr(object_in_question, "__getitem__")
测试一个可迭代的 . 你需要检查两者,因为str
没有__iter__
方法(至少不在Python 2中,在Python 3中没有),因为generator
对象没有__getitem__
方法 .在我的脚本中,我经常发现定义
iterable
函数很方便 . (现在纳入了Alfe建议的简化):所以你可以用非常易读的形式测试任何对象是否可以迭代
就像使用
callable
函数一样编辑:如果你安装了numpy,你可以简单地做:从
numpy import iterable
,这就像是如果你没有numpy,你可以简单地实现这个代码,或上面的代码 .
尊重Python的duck typing最简单的方法是捕获错误(Python完全知道它对一个对象成为迭代器的期望):
Notes :
对象的区别是无关紧要的如果异常类型相同,则可迭代,或者已经实现了错误的
__iter__
:无论如何,您将无法迭代该对象 .我想我理解你的关注:如果
callable
如果没有为我的对象定义__call__
,如果我还可以依赖鸭子打字来提升AttributeError
,那么callable
是如何存在的,但是这不是可迭代检查的情况?我不知道答案,但你可以实现我(和其他用户)给出的函数,或者只是在你的代码中捕获异常(你在那部分的实现就像我写的函数 - 只是确保你隔离了从其余代码创建迭代器,这样您就可以捕获异常并将其与另一个异常
TypeError
区分开来 .您可以检查
__len__
属性,而不是检查__iter__
属性,该属性由每个python内置可迭代实现,包括字符串 .由于显而易见的原因,无法迭代的对象不会实现这一点 . 但是,它不会捕获不实现它的用户定义的iterables,也不会捕获
iter
可以处理的生成器表达式 . 但是,这可以在一行中完成,添加一个简单的or
表达式检查生成器将解决这个问题 . (注意写type(my_generator_expression) == generator
会抛出NameError
. 请改为参考this . )(这使得检查是否可以在对象上调用
len
非常有用 . )除了常规尝试和除外,你可以运行帮助 .
help将提供可以在该对象上运行的所有方法(它可以是任何对象,并且可能不是示例中的列表),在这种情况下是临时的 .
注意:这将是您手动执行的操作 .