在 Python 中,如何确定对象是不是可迭代?

Posted

技术标签:

【中文标题】在 Python 中,如何确定对象是不是可迭代?【英文标题】:In Python, how do I determine if an object is iterable?在 Python 中,如何确定对象是否可迭代? 【发布时间】:2010-12-29 11:26:14 【问题描述】:

isiterable这样的方法吗?到目前为止我找到的唯一解决方案是调用

hasattr(myObj, '__iter__')

但我不确定这是多么简单。

【问题讨论】:

__getitem__ 也足以使对象可迭代 FWIW:如果isinstance(myObj, dict)iter(myObj) 成功,因此如果您正在查看可能是dicts 序列或单个dictmyObj,您将在这两种情况下都成功。如果您想知道什么是序列,什么不是序列,这一点很重要。 (在 Python 2 中) __getitem__ 也足以使对象可迭代...如果它从零索引开始 【参考方案1】:

有很多方法可以检查对象是否可迭代:

from collections.abc import Iterable
myobject = 'Roster'
  
if isinstance(myobject , Iterable):
    print(f"myobject  is iterable") 
else:
   print(f"strong textmyobject  is not iterable")

【讨论】:

【参考方案2】:

我想更深入地了解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__ 方法。

    检查IterableSequence 的实例,或检查 属性__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 循环时会发生什么。如果您已经知道,请随时直接跳到下一部分。

当您将for item in o 用于某些可迭代对象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__ 属性并且不被视为IterableSequence 的实例:

>>> from collections import Iterable, Sequence
>>> hasattr(b, '__iter__')
False
>>> isinstance(b, Iterable)
False
>>> isinstance(b, Sequence)
False

这就是为什么 Fluent Python by Luciano Ramalho 建议调用 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: '***', 2: '!',
...                   4: 'not printed', 
...                   'x': 'not printed')
>>> for x in w:
...     print(x)
... 
hi
***
!

关于第 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__。考虑以下类

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__ 的特殊处理,并且可能是 将来会消失(尽管在我写这篇文章时它并没有被弃用)。

【讨论】:

所以通过在try 块中返回True 和在except TypeError 块中返回False 来定义谓词is_iterable 是安全的吗? 这是一个很好的答案。我认为它突出了 getitem 协议的不直观和不幸的性质。它不应该被添加。【参考方案3】:

我最近一直在研究这个问题。基于此,我的结论是,现在这是最好的方法:

from collections.abc import Iterable   # drop `.abc` with Python 2.7 or lower

def iterable(obj):
    return isinstance(obj, Iterable)

上面已经推荐过,但普遍的共识是使用iter()会更好:

def iterable(obj):
    try:
        iter(obj)
    except Exception:
        return False
    else:
        return True

我们也在代码中使用了iter() 来达到这个目的,但我最近开始对只有__getitem__ 被认为是可迭代的对象感到越来越恼火。有正当理由将__getitem__ 放在不可迭代的对象中,并且上面的代码不能很好地工作。作为一个现实生活中的例子,我们可以使用Faker。上面的代码报告它是可迭代的,但实际上试图迭代它会导致AttributeError(用 Faker 4.0.2 测试):

>>> from faker import Faker
>>> fake = Faker()
>>> iter(fake)    # No exception, must be iterable
<iterator object at 0x7f1c71db58d0>
>>> list(fake)    # Ooops
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/.../site-packages/faker/proxy.py", line 59, in __getitem__
    return self._factory_map[locale.replace('-', '_')]
AttributeError: 'int' object has no attribute 'replace'

如果我们使用insinstance(),我们不会意外地认为Faker 实例(或任何其他只有__getitem__ 的对象)是可迭代的:

>>> from collections.abc import Iterable
>>> from faker import Faker
>>> isinstance(Faker(), Iterable)
False

较早的答案评论说使用iter() 更安全,因为在Python 中实现迭代的旧方法是基于__getitem__isinstance() 方法不会检测到这一点。对于旧的 Python 版本,这可能是正确的,但根据我相当详尽的测试,isinstance() 现在效果很好。 isinstance() 不起作用但 iter() 起作用的唯一情况是使用 Python 2 时使用 UserDict。如果相关,可以使用 isinstance(item, (Iterable, UserDict)) 来解决这个问题。

【讨论】:

同样typing.Dictiter(Dict) 认为是可迭代的,但list(Dict) 失败并出现错误TypeError: Parameters to generic types must be types. Got 0.。正如预期的那样,isinstance(Dict, Iterable) 返回 false。 我得出了相同的结论,但原因不同。使用iter 导致我们的一些使用“预缓存”的代码不必要地减慢了速度。如果__iter__ 代码很慢,那么在您只想查看是否可迭代的任何时候调用iter...。 是否值得在最后一点添加注释,指出开发人员不再积极支持 Python 2,如果 Python 3 是选项?【参考方案4】:

参加聚会有点晚了,但我问了自己这个问题,看到了这个问题,然后想到了一个答案。不知道有没有人发过这个。但本质上,我注意到所有可迭代类型的字典中都有 __getitem__()。这就是您无需尝试即可检查对象是否为可迭代对象的方式。 (双关语)

def is_attr(arg):
    return '__getitem__' in dir(arg)

【讨论】:

不幸的是,这是不可靠的。 Example 设置对象是另一个反例。 这是什么双关语?【参考方案5】:

    检查__iter__ 适用于序列类型,但它会失败,例如Python 2 中的字符串。我也想知道正确的答案,在那之前,这是一种可能性(也适用于字符串):

    from __future__ import print_function
    
    try:
        some_object_iterator = iter(some_object)
    except TypeError as te:
        print(some_object, 'is not iterable')
    

    iter 内置检查 __iter__ 方法或在字符串的情况下检查 __getitem__ 方法。

    另一种通用的pythonic方法是假设一个可迭代的,然后如果它不适用于给定的对象,则优雅地失败。 Python 词汇表:

    Pythonic 编程风格通过检查对象的方法或属性签名而不是通过与某些类型对象的显式关系来确定对象的类型(“如果它看起来像 duck 并且像 鸭子,它必须是一个鸭子。”)通过强调接口而不是特定类型,精心设计的代码通过允许多态替换来提高其灵活性。 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__ 迭代的类。

【讨论】:

[e for e in my_object] 可能会因其他原因引发异常,即my_object 未定义或my_object 实现中可能存在错误。 一个字符串一个序列(isinstance('', Sequence) == True并且作为任何序列它可迭代的(isinstance('', Iterable) )。虽然hasattr('', '__iter__') == False 可能会令人困惑。 如果my_object 非常大(比如说,像itertools.count() 这样的无限大),您的列表理解将占用大量时间/内存。最好制作一个生成器,它永远不会尝试构建(可能是无限的)列表。 如果 some_object 也抛出由其他原因(错误等)引起的 TypeError 怎么办?我们如何从“Not iterable TypeError”中分辨出来? 请注意,在 Python 3 中:hasattr(u"hello", '__iter__') 返回 True【参考方案6】:

不是真的“正确”,但可以快速检查最常见的类型,如字符串、元组、浮点数等......

>>> '__iter__' in dir('sds')
True
>>> '__iter__' in dir(56)
False
>>> '__iter__' in dir([5,6,9,8])
True
>>> '__iter__' in dir('jh':'ff')
True
>>> '__iter__' in dir('jh')
True
>>> '__iter__' in dir(56.9865)
False

【讨论】:

【参考方案7】:

鸭子打字

try:
    iterator = iter(theElement)
except TypeError:
    # not iterable
else:
    # iterable

# for obj in iterator:
#     pass

类型检查

使用Abstract Base Classes。它们至少需要 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() 更可靠一点,如by the documentation 所述:

检查 isinstance(obj, Iterable) 检测到的类 注册为 Iterable 或具有__iter__() 方法,但是 它不检测使用__getitem__() 迭代的类 方法。唯一可靠的方法来确定一个对象 可迭代的是调用iter(obj)

【讨论】:

来自 Luciano Ramalho 的“Fluent Python”:从 Python 3.4 开始,检查对象 x 是否可迭代的最准确方法是调用 iter(x) 并处理 TypeError 异常(如果不是)吨。这比使用 isinstance(x, abc.Iterable) 更准确,因为 iter(x) 还考虑了遗留的 getitem 方法,而 Iterable ABC 没有。 如果您在想“哦,我将只使用 isinstance(x, (collections.Iterable, collections.Sequence)) 而不是 iter(x)”,请注意,这仍然不会检测到仅实现 __getitem__ 而不是 @ 的可迭代对象987654333@。使用iter(x) 并捕获异常。 在对象上调用 iter() 可能是一项昂贵的操作(请参阅 Pytorch 中的 DataLoader,它在 iter() 上分叉/生成多个进程)。 似乎 enumerate() 与 iter() (Python 3)具有相同的效果,如果您接下来要做的是枚举,这可能会简化一些事情序列 - 无需事先显式 iter(),因为 enumerate() 将在必要时自行引发适当的异常。【参考方案8】:

我一直不明白为什么 python 有 callable(obj) -&gt; bool 而不是 iterable(obj) -&gt; bool... 当然hasattr(obj,'__call__') 会更容易,即使它更慢。

由于几乎所有其他答案都建议使用try/except TypeError,在任何语言中测试异常通常被认为是不好的做法,这是iterable(obj) -&gt; bool 的实现,我越来越喜欢并经常使用:

为了 python 2,我将使用 lambda 来提高性能... (在 python 3 中,你使用什么来定义函数并不重要,def 的速度与lambda 大致相同)

iterable = lambda obj: hasattr(obj,'__iter__') or hasattr(obj,'__getitem__')

请注意,此函数对带有__iter__ 的对象执行得更快,因为它不测试__getitem__

大多数可迭代对象应依赖__iter__,其中特殊情况对象回退到__getitem__,尽管对象可迭代需要其中任何一个。 (因为这是标准的,它也会影响 C 对象)

【讨论】:

他没有提供工作代码,更不用说谈论 python 性能了......虽然这个答案真的只是为了方便,就像我在这里看到的很多次一样。【参考方案9】:

您可以检查__len__ 属性,而不是检查__iter__ 属性,该属性由每个python 内置迭代实现,包括字符串。

>>> hasattr(1, "__len__")
False
>>> hasattr(1.3, "__len__")
False
>>> hasattr("a", "__len__")
True
>>> hasattr([1,2,3], "__len__")
True
>>> hasattr(1,2, "__len__")
True
>>> hasattr("a":1, "__len__")
True
>>> hasattr(("a", 1), "__len__")
True

由于显而易见的原因,不可迭代的对象不会实现这一点。但是,它不会捕获未实现它的用户定义的可迭代对象,也不会捕获 iter 可以处理的生成器表达式。然而,这可以在一行中完成,添加一个简单的or 表达式检查生成器将解决这个问题。 (请注意,写type(my_generator_expression) == generator 会抛出NameError。请参阅this 答案。)

您可以从类型中使用 GeneratorType:

>>> import types
>>> types.GeneratorType
<class 'generator'>
>>> gen = (i for i in range(10))
>>> isinstance(gen, types.GeneratorType)
True

--- utdemir 接受的答案

(这有助于检查您是否可以在对象上调用len。)

【讨论】:

不幸的是,并非所有可迭代对象都使用__len__... 在这种情况下,通常是计算两个对象之间距离的不当使用。其中obj.dist() 可以轻松替换。 是的。大多数用户定义的可迭代对象实现了 iter 和 getitem,但没有实现 len。但是,内置类型可以,如果您想检查是否可以在其上调用 len 函数,检查 len 更安全。但你是对的。【参考方案10】:

Python 3.5 开始,您可以使用标准库中的 typing 模块来处理与类型相关的事情:

from typing import Iterable

...

if isinstance(my_item, Iterable):
    print(True)

【讨论】:

这将为单个字符串对象返回 True,仅供参考。 @waydegg 是的,字符串是可迭代的。 在 Python 3.6 中,此代码不起作用。在 3.7 中它确实有效。看起来它将是 3.9 中的deprecated。 typing 用于类型检查工具(例如 MyPyPyCharm)并且不保证这种行为。我认为您的意思是从 collections.abc 导入 Iterable 类。【参考方案11】:
def is_iterable(x):
    try:
        0 in x
    except TypeError:
        return False
    else:
        return True

这将对所有形式的可迭代对象说“是”,但它会对 Python 2 中的字符串说“不”。 (这就是我想要的,例如,当递归函数可以采用字符串或字符串容器时。在这种情况下,asking forgiveness 可能会导致混淆代码,最好先请求许可。)

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)

这里的许多其他策略都会对字符串说“是”。如果这是您想要的,请使用它们。

import collections
import numpy

assert isinstance("string", collections.Iterable)
assert isinstance("string", collections.Sequence)
assert numpy.iterable("string")
assert iter("string")
assert hasattr("string", '__getitem__')

注意:is_iterable() 将对bytesbytearray 类型的字符串表示“是”。

bytes Python 3 中的对象是可迭代的 True == is_iterable(b"string") == is_iterable("string".encode('utf-8')) Python 2 中没有这种类型。 bytearray Python 2 和 3 中的对象是可迭代的 True == is_iterable(bytearray(b"abc"))

O.P. hasattr(x, '__iter__') 方法将在 Python 3 中对字符串说是,在 Python 2 中说否(无论是''b'' 还是u'')。感谢@LuisMasuelli 注意到它也会让你在一辆越野车__iter__ 上失望。

【讨论】:

【参考方案12】:

迄今为止我发现的最佳解决方案:

hasattr(obj, '__contains__')

它主要检查对象是否实现in 运算符。

优势(其他解决方案都没有这三个):

它是一个表达式(用作 lambda,与 try...except 变体相反) 它(应该)由所有可迭代对象实现,包括 字符串(与 __iter__ 相对) 适用于任何 Python >= 2.5

注意事项:

“请求宽恕,而不是许可”的 Python 哲学在以下情况下效果不佳:在一个列表中,您同时拥有可迭代对象和不可迭代对象,并且您需要根据每个元素的类型对每个元素进行不同的处理(尝试处理可迭代对象,而除了 处理非可迭代对象,但它看起来很糟糕丑陋和误导) 尝试实际迭代对象(例如 [x for x in obj])以检查它是否可迭代的此问题的解决方案可能会对大型可迭代对象造成显着的性能损失(特别是如果您只需要对象的前几个元素)可迭代的,例如),应该避免

【讨论】:

很好,但为什么不使用***.com/questions/1952464/… 中建议的集合模块呢?对我来说似乎更具表现力。 它更短(并且不需要额外的导入)而不会失去任何清晰度:使用“包含”方法感觉像是一种检查某物是否是对象集合的自然方法。 某物可以包含某物并不一定意味着它是可迭代的。例如,用户可以检查一个点是否在 3D 立方体中,但您将如何遍历该对象? 这是不正确的。可迭代对象本身不支持 contains,至少在 Python 3.4 中是这样。【参考方案13】:

如果对象是可迭代的,则以下代码中的isiterable 函数返回True。如果它不可迭代返回False

def isiterable(object_):
    return hasattr(type(object_), "__iter__")

例子

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

【讨论】:

上面有这么多详细的答案,还有很多赞成票,而你却给出了一个无法解释的答案……嗯 请不要发布裸代码。还要说明这是做什么的。【参考方案14】:

pandas 有一个这样的内置函数:

from pandas.util.testing import isiterable

【讨论】:

这只是查看是否有__iter__,并不真正关心序列和类似的。【参考方案15】:

这还不够:__iter__ 返回的对象必须实现迭代协议(即next 方法)。请参阅documentation 中的相关部分。

在 Python 中,一个好的做法是“试看”而不是“检查”。

【讨论】:

“鸭子打字”我相信? :) @willem: 或“不求许可,只求宽恕” ;-) @willem "permission" 和 "forgiveness" 样式都符合鸭式打字的条件。如果你问一个对象可以做什么而不是它是什么,那就是鸭式打字。如果你使用内省,那就是“许可”;如果你只是尝试这样做,看看它是否有效,那就是“宽恕”。 更多关于不要问Python能为你做什么,要问你能为Python做什么工作【参考方案16】:

尊重 Python 的 duck typing,最简单的方法是捕获错误(Python 完全知道它期望对象成为迭代器的内容):

class A(object):
    def __getitem__(self, item):
        return something

class B(object):
    def __iter__(self):
        # Return a compliant iterator. Just an example
        return iter([])

class C(object):
    def __iter__(self):
        # Return crap
        return 1

class D(object): pass

def iterable(obj):
    try:
        iter(obj)
        return True
    except:
        return False

assert iterable(A())
assert iterable(B())
assert iterable(C())
assert not iterable(D())

注意事项

    如果异常类型相同,则对象是否不可迭代或是否已实现错误__iter__ 无关紧要:无论如何,您将无法迭代对象。

    我想我理解您的担忧:如果没有为我的对象定义 __call__callable 如何作为检查是否还可以依赖鸭子类型来引发 AttributeError,但情况并非如此可迭代检查?

    我不知道答案,但是您可以实现我(和其他用户)提供的功能,或者只是在您的代码中捕获异常(您在该部分中的实现将与我编写的功能一样 - 只需确保您将迭代器的创建与其余代码隔离开来,这样您就可以捕获异常并将其与另一个 TypeError 区分开来。

【讨论】:

【参考方案17】:

在 Python

但从 Python 2.6 和 3.0 开始,您可以利用新的 ABC(抽象基类)基础架构以及集合模块中提供的一些内置 ABC:

from collections import Iterable

class MyObject(object):
    pass

mo = MyObject()
print isinstance(mo, Iterable)
Iterable.register(MyObject)
print isinstance(mo, Iterable)

print isinstance("abc", Iterable)

现在,这是可取的还是实际可行的,只是约定问题。如您所见,您可以将不可迭代的对象注册为 Iterable - 它会在运行时引发异常。因此,isinstance 获得了“新”含义——它只检查“已声明”类型的兼容性,这是 Python 中的一个好方法。

另一方面,如果你的对象不满足你需要的接口,你会怎么做?举个例子:

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

如果对象不满足您的期望,您只需抛出 TypeError,但如果已注册正确的 ABC,您的检查将无用。相反,如果__iter__ 方法可用,Python 会自动将该类的对象识别为可迭代对象。

所以,如果你只是期望一个可迭代的,迭代它并忘记它。另一方面,如果您需要根据输入类型执行不同的操作,您可能会发现 ABC 基础架构非常有用。

【讨论】:

不要在初学者的示例代码中使用裸露的except:。它助长了不良做法。 JFS:我不会,但我需要经历多个引发异常的代码,我不想捕获特定的异常......我认为这段代码的目的很明确.【参考方案18】:

我找到了一个不错的解决方案here:

isiterable = lambda obj: isinstance(obj, basestring) \
    or getattr(obj, '__iter__', False)

【讨论】:

【参考方案19】:

根据Python 2 Glossary,iterables 是

所有序列类型(例如liststrtuple)和一些非序列类型,例如dictfile,以及您使用__iter__() 或@ 定义的任何类的对象987654328@ 方法。 Iterables 可用于 for 循环和许多其他需要序列的地方(zip()、map()、...)。当一个可迭代对象作为参数传递给内置函数 iter() 时,它会为该对象返回一个迭代器。

当然,鉴于 Python 的一般编码风格基于“请求宽恕比许可更容易”这一事实。一般的期望是使用

try:
    for i in object_in_question:
        do_something
except TypeError:
    do_something_for_non_iterable

但是如果你需要明确地检查它,你可以通过hasattr(object_in_question, "__iter__") or hasattr(object_in_question, "__getitem__") 测试一个可迭代的。您需要检查两者,因为strs 没有__iter__ 方法(至少在Python 2 中没有,在Python 3 中有)并且因为generator 对象没有__getitem__ 方法.

【讨论】:

【参考方案20】:

我经常发现在我的脚本中定义 iterable 函数很方便。 (现在包含了 Alfe 建议的简化):

import collections

def iterable(obj):
    return isinstance(obj, collections.Iterable):

这样您就可以测试是否有任何对象可以以非常易读的形式进行迭代

if iterable(obj):
    # act on iterable
else:
    # not iterable

就像你对callable 函数所做的那样

编辑:如果你安装了 numpy,你可以简单地做:来自numpy import iterable, 这就像

def iterable(obj):
    try: iter(obj)
    except: return False
    return True

如果你没有numpy,你可以简单地实现这个代码,或者上面那个。

【讨论】:

每当你像if x: return True else: return Falsex 是布尔值)你可以写成return x。在你的情况下return isinstance(…) 没有任何if 既然您承认 Alfe 的解决方案更好,为什么不编辑您的答案以简单地说出来?相反,您现在的答案中有两个版本。不必要的冗长。提交修改以解决此问题。 您应该在 ` except: return False ` 行中捕获“TypeError”。抓住一切是一种糟糕的模式。 知道。我从使用通用异常的 NumPy 库中翻译了那段代码。 仅仅因为代码取自 NumPy 并不意味着它是好的......模式与否,唯一应该做的就是捕捉所有事情是如果你在你的程序中明确地处理错误。 【参考方案21】:
try:
  #treat object as iterable
except TypeError, e:
  #object is not actually iterable

不要运行检查来查看你的鸭子是否真的是一只鸭子以查看它是否可迭代,将其视为可迭代,如果不是则抱怨。

【讨论】:

从技术上讲,在迭代过程中,您的计算可能会抛出 TypeError 并让您离开这里,但基本上是的。 @willem:请使用 timeit 执行基准测试。 Python 异常通常比 if 语句更快。他们可以通过解释器采取更短的路径。 @willem:IronPython 有慢速(与 CPython 相比)异常。 一个有效的尝试:语句真的很快。所以如果你有很少的例外,try-except 很快。如果您预计会有很多异常,“if”会更快。 不应该通过在TypeError之后添加“as e”而不是添加“, e”来捕获异常对象吗?【参考方案22】:

你可以试试这个:

def iterable(a):
    try:
        (x for x in a)
        return True
    except TypeError:
        return False

如果我们可以创建一个迭代它的生成器(但永远不要使用生成器,这样它就不会占用空间),它是可迭代的。似乎是“duh”之类的东西。为什么首先需要确定变量是否可迭代?

【讨论】:

iterable(itertools.repeat(0)) 怎么样? :) @badp,(x for x in a) 只是创建了一个生成器,它不对 a 进行任何迭代。 尝试(x for x in a) 是否完全等同于尝试iterator = iter(a)?或者在某些情况下两者是不同的? for _ in a: break 不是更直接吗?慢吗? @Mr_and_Mrs_D 如果被测对象是一个之后迭代的迭代器,那就太糟糕了(因为它的位置无法重置,所以它会短 1 项),创建垃圾生成器不会迭代对象因为它们没有被迭代,但我不确定如果不可迭代它会 100% 引发 TypeError。

以上是关于在 Python 中,如何确定对象是不是可迭代?的主要内容,如果未能解决你的问题,请参考以下文章

如何检查一个对象在 Python 中是不是可迭代? [复制]

Python中的迭代器

python学习--如何实现可迭代对象(itearable)和迭代器(iterator)

迭代器

Python迭代器是啥?

python