Python:如何确定一个对象是否可迭代?

2024-12-03 08:45:00
admin
原创
146
摘要:问题描述:有没有类似的方法isiterable?到目前为止,我发现的唯一解决方案是调用:hasattr(myObj, '__iter__') 但我不确定这是否万无一失。解决方案 1:检查__iter__序列类型有效,但对于 Python 2 中的字符串等会失败。我也想知道正确答案,在此之前,这里有一种可能性(...

问题描述:

有没有类似的方法isiterable?到目前为止,我发现的唯一解决方案是调用:

hasattr(myObj, '__iter__')

但我不确定这是否万无一失。


解决方案 1:

  1. 检查__iter__序列类型有效,但对于 Python 2 中的字符串等会失败。我也想知道正确答案,在此之前,这里有一种可能性(也适用于字符串):

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

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

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

Python 式的编程风格,通过检查对象的方法或属性签名来确定对象的类型,而不是通过与某个类型对象的显式关系来确定(“如果它看起来像鸭子叫起来也像鸭子,那它一定是鸭子。”)通过强调接口而不是特定类型,设计良好的代码可以通过允许多态替换来提高其灵活性。鸭子类型避免使用 type() 或 isinstance() 进行测试。相反,它通常采用 EAFP(更容易请求原谅而不是许可)编程风格。

...

try:
   _ = (e for e in my_object)
except TypeError:
   print(my_object, 'is not iterable')
  1. collections模块提供了一些抽象基类,允许询问类或实例是否提供特定功能,例如:

from collections.abc import Iterable

if isinstance(e, Iterable):
    # e is iterable

但是,这不会检查可通过 迭代的类__getitem__

解决方案 2:

鸭子类型

try:
    iterator = iter(the_element)
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(the_element, Iterable):
    # iterable
else:
    # not iterable

但是,正如文档中iter()所述,它更可靠一些:

检查会isinstance(obj, Iterable)检测已注册为 Iterable 或具有__iter__()方法的类,但不会检测使用该__getitem__()
方法进行迭代的类。确定对象是否可迭代的唯一可靠方法是调用iter(obj)

解决方案 3:

我想进一步阐明 和 之间的相互作用iter以及__iter__幕后__getitem__发生的事情。有了这些知识,您将能够理解为什么您能做的最好的事情是

try:
    iter(maybe_iterable)
    print('iteration will probably work')
except TypeError:
    print('not iterable')

我将首先列出事实,然后快速提醒您在forPython 中使用循环时会发生什么,然后进行讨论以说明事实。

事实

  1. 如果以下条件至少有一个成立,则可以o通过调用从任何对象获取迭代器: a)具有返回迭代器对象的方法。迭代器是具有和( Python 2: ) 方法的任何对象。b )具有方法。iter(o)

o`__iter____iter__next`next

o`__getitem__`

  1. Iterable检查或的实例Sequence,或者检查属性__iter__是不够的。

  2. 如果一个对象o只实现了__getitem__,但没有实现__iter__,则会构造一个迭代器,尝试从整数索引(从索引 0 开始)iter(o)获取项目。迭代器将捕获引发的任何错误(但没有其他错误),然后引发自身。o`IndexError`StopIteration

  3. iter从最普遍的意义上讲,除了尝试一下之外,没有其他方法可以检查返回的迭代器是否合理。

  4. 如果对象o实现了__iter__,则该iter函数将确保 返回的对象__iter__是迭代器。如果对象仅实现了 ,则不会进行健全性检查__getitem__

  5. __iter__获胜。如果一个对象o同时实现了__iter____getitem__iter(o)则将调用__iter__

  6. 如果您想让自己的对象可迭代,请始终实现该__iter__方法。

for循环

为了跟上进度,您需要了解for在 Python 中使用循环时会发生什么。如果您已经知道,请直接跳到下一部分。

for item in o当您对某个可迭代对象使用 时o,Python 会调用iter(o)并期望迭代器对象作为返回值。迭代器是任何实现__next__(或next在 Python 2 中) 方法和__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

iter使用的实例调用将BasicIterable返回一个迭代器,而不会出现任何问题,因为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__方法,而IterableABC 没有。

第 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循环遍历 a可以按预期工作的原因: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

请注意,对的调用__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)为对象 调用时oiter将确保 的返回值__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成功,但在第一次调用时会引发异常__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__如下:

  1. 如果您实现__iter__,实例将被视为可迭代,并将isinstance(o, collections.abc.Iterable)返回True

  2. 如果返回的对象__iter__不是迭代器,iter将立即失败并引发TypeError

  3. 出于向后兼容的原因,对 进行了特殊处理__getitem__。再次引用 Fluent Python 的话:

这就是为什么任何 Python 序列都是可迭代的:它们都实现了__getitem__。事实上,标准序列也实现了__iter__,你的序列也应该实现 ,因为 的特殊处理是__getitem__出于向后兼容的原因而存在的,将来可能会消失(尽管在我写这篇文章时它还没有被弃用)。

解决方案 4:

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

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__存在正当理由,而上面的代码无法很好地工作。作为一个现实生活中的例子,我们可以使用Faker。上面的代码报告它是可迭代的,但实际上尝试迭代它会导致(使用 Faker 4.0.2 测试):__getitem__`AttributeError`

>>> 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()起作用的情况是UserDict使用 Python 2 时。如果这相关,可以使用isinstance(item, (Iterable, UserDict))来解决这个问题。

解决方案 5:

Python 3.5开始,你可以使用标准库中的typing模块来处理类型相关的操作:

from typing import Iterable

...

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

解决方案 6:

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

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

解决方案 7:

在 Python <= 2.5 中,您不能也不应该这样做 - iterable 是一个“非正式”接口。

但是从 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 会自动将该类的对象识别为 Iterable。

因此,如果您只是期望一个可迭代对象,请对其进行迭代并忘掉它。另一方面,如果您需要根据输入类型执行不同的操作,您可能会发现 ABC 基础结构非常有用。

解决方案 8:

try:
  #treat object as iterable
except TypeError, e:
  #object is not actually iterable

不要检查你的鸭子是否真的是鸭子,看看它是否可迭代,而要像对待可迭代对象一样对待它,如果不是,则抱怨。

解决方案 9:

你可以尝试这个:

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

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

解决方案 10:

到目前为止我发现的最佳解决方案:

hasattr(obj, '__contains__')

它主要检查对象是否实现了in操作符。

优点(其他解决方案均不具备这三个优点):

  • 它是一个表达式(作为lambda工作,而不是try...except变体)

  • 它(应该)由所有可迭代对象实现,包括字符串(而不是__iter__

  • 适用于任何 Python >= 2.5

笔记:

  • Python 的“请求原谅,而不是请求许可”的哲学在以下情况下并不适用:例如,在一个列表中,你同时拥有可迭代对象和不可迭代对象,并且你需要根据其类型对每个元素进行不同的处理(在 try 上处理可迭代对象,在 except 上处理不可迭代对象可行的,但看起来非常丑陋且具有误导性)

  • 这个问题的解决方案是尝试实际迭代对象(例如 [x for x in obj])来检查它是否可迭代,这可能会对大型迭代器造成严重的性能损失(特别是如果你只需要迭代器的前几个元素),应该避免

解决方案 11:

我在这里找到了一个很好的解决方案:

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

解决方案 12:

根据Python 2 词汇表,可迭代对象是

所有序列类型(例如liststrtuple)和一些非序列类型,例如dictfile以及使用__iter__()or__getitem__()方法定义的任何类的对象。可迭代对象可用于 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__

解决方案 13:

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

import collections

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

因此你可以以非常易读的形式测试任何对象是否可迭代

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

callable就像你用函数做的那样

编辑:如果你安装了 numpy,你可以简单地执行:from numpy import iterable,它就像

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

如果您没有 numpy,您可以简单地实现此代码或上面的代码。

解决方案 14:

熊猫有一个内置函数如下:

from pandas.util.testing import isiterable

解决方案 15:

def is_iterable(x):
    try:
        0 in x
    except TypeError:
        return False
    else:
        return True

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

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() 对类型bytes和的字符串将回答“是” bytearray

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

  • bytearrayPython 2 和 3 中的对象是可迭代的True == is_iterable(bytearray(b"abc"))

OPhasattr(x, '__iter__')方法将对 Python 3 中的字符串说是,对 Python 2 中的字符串说否(无论是否''b''u'')。感谢 @LuisMasuelli 注意到它还会让您对有缺陷的 感到失望__iter__

解决方案 16:

我一直不明白为什么 python 有callable(obj) -> bool但却没有iterable(obj) -> bool...当然,即使速度较慢,

它也更容易做到。hasattr(obj,'__call__')

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

为了 Python 2,我将使用 lambda 来获得额外的性能提升...

(在 Python 3 中,使用什么来定义函数并不重要,def速度大致相同lambda

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

__iter__请注意,由于此函数不测试,因此对于具有的对象,执行速度更快__getitem__

大多数可迭代对象应该依赖于__iter__特殊情况对象回退到的位置__getitem__,尽管对象需要其中任何一个才能可迭代。

(并且因为这是标准,它也会影响 C 对象)

解决方案 17:

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

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

解决方案 18:

最简单的方式,尊重 Python 的鸭子类型,就是捕获错误(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())

注意

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

  2. 我想我明白你的担心:如果我也可以依靠鸭子类型来引发我的对象的if is not defined ,那么如何callable检查 exist ?但对于可迭代检查来说情况并非如此?AttributeError`__call__`

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

解决方案 19:

如果对象是可迭代的,则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

解决方案 20:

在我的代码中我曾经检查不可迭代的对象:

hasattr(myobject,'__trunc__')

这非常快,并且也可以用来检查可迭代对象(使用not)。

我不能 100% 确定此解决方案是否适用于所有对象,也许其他人可以提供更多背景信息。方法似乎与数值类型有关(所有可以四舍五入为整数的对象都需要它)。但我没有找到任何包含或的__trunc__对象。__trunc__`__iter__`__getitem__

解决方案 21:

__iter__您可以检查属性,而不是检查__len__属性,该属性由每个 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。请改为参考此答案。)

您可以从类型中使用 GeneratorType:

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

--- utdemir 接受的答案

(这使得检查您是否可以调用len该对象变得有用。)

解决方案 22:

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

>>> '__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

解决方案 23:

有点迟了,但我问了自己这个问题,然后看到了这个,然后想到了一个答案。我不知道是否有人已经发布了这个。但本质上,我注意到所有可迭代类型的字典中都有__getitem__()。这就是你无需尝试就可以检查对象是否是可迭代对象的方法。(双关语)

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

解决方案 24:

在尝试了各种函数之后,我相信这是可靠地判断对象是否可迭代的最快方法,尽管它涉及到一些例外:

def isiterable(x):
    if  hasattr(x, '__iter__'): return not isinstance(x, type)
    if hasattr(x, '__getitem__'):
        try:
            x[0]
            return True
        except Exception:
            return False
    return False

这不是一个完美的解决方案,但正如我下面所解释的那样,这是最可靠的解决方案之一。我能想到的两种极端情况是此函数会失败:第一种是具有__getitem__方法和的不可迭代对象x[0]。第二种是可迭代type对象。

如果您不想要裸异常,下面我描述了几种方法,它们都有不同的缺点。

哪些内容可以迭代

为了可迭代,对象需要__getitem____iter__方法。

但是有些对象__getitem__是不可迭代的,例如:

class Getitem_ButNotIterable:
    def __init__(self): self.dict_ = {"a":1, "b":2}
    def __getitem__(self, item:str): return self.dict_[item]

for i in Getitem_ButNotIterable(): 
    pass

>> KeyError: 0

值得一提的是,除非您实际尝试对其进行迭代,否则运行iter(Getitem_ButNotIterable())不会引发异常,因此该方法并不完全可靠。

类型可以具有__iter__属性,但它们(通常)也不可迭代。

hasattr(list, "__iter__") >> True
for i in list: pass >> TypeError: 'type' object is not iterable

然而类型实际上是可以迭代的,也就是说如果它们的元类有__iter__方法的话。

最困难的情况是这样的:

class Getitem_ButNotIterable2:
    def __init__(self): self.dict_ = {0:1, "b":2}
    def __getitem__(self, item:str): return self.dict_[item]

如果您尝试迭代它,它将调用x[0]which 返回 0,但随后它将调用x[1]which 引发KeyError,而 iter 不会捕获该错误。不幸的是,我认为没有简单的方法来检测这些错误。

测试对象是否可迭代的方法

为了测试各种方法,我创建了 40 个不同的可迭代对象和不可迭代对象,并在每个可迭代对象/不可迭代对象上尝试了 1_000_000 次函数,并写下了所有操作所花费的总时间。开头的方法需要 8 秒作为参考。


from collections.abc import Iterable
def is_iterable1(x): 
    return isinstance(x, Iterable)

耗时 17 秒。这将检查对象是否有__iter__方法,因此它将无法识别仅有的可迭代对象__getitem__

值得一提的是,如果您使用已弃用的版本typing.Iterable,时间将增加到 50 秒。


def is_iterable2(x): 
    return hasattr(x, "__iter__")

耗时 5 秒。这几乎相当于isinstance(x, Iterable),但是还有额外的误报:类型,如is_iterable2(list)将返回 True。


def is_iterable3(x): 
    return hasattr(x, "__iter__") and hasattr(x, "__getitem__")

耗时 7 秒。与上述两种方法不同,该方法可以识别可迭代对象,但也会对不可迭代对象和使用这些方法的类型__getitem__给出误报__getitem__


def is_iterable4(x):
    try:
        iter(x)
        return True
    except TypeError:
       return False
    return True

耗时 36 秒。这将对具有 的不可迭代对象产生误报__getitem__。更改iter(x)(i for i in x)是等效的。如果您不想要裸露的异常,这是一种好方法。


def is_iterable5(x):
    try:
        0 in x
    except TypeError:
        return False
    else:
        return True

耗时 50 秒。这将对具有 的不可迭代对象引发异常__getitem__


def is_iterable6(x):
    try: 
        for _ in x: return True
    except Exception: return False
    return True

耗时 33 秒。我们现在遇到了一些例外,但我想不出任何其他方法可以让它更可靠。可能存在一些极端情况,在这些情况下,您不想在通常用途之外迭代某些对象,因为这可能会扰乱内部状态。


def is_iterable7(x):
    if  hasattr(x.__class__, '__iter__'): return True
    if hasattr(x, '__getitem__'):
        try:
            x[0]
            return True
        except Exception:
            return False
    return False

耗时 23 秒。几乎可以正确评估所有内容,因为它不会对具有__iter__方法的类型给出真否定。仍然有两种边缘情况:具有的不可迭代对象__getitem__和具有具有__iter__方法的元类的可迭代类。但是,我认为不可能比这更可靠,


def is_iterable8(x):
    if  hasattr(x, '__iter__'): return not isinstance(x, type)
    if hasattr(x, '__getitem__'):
        try:
            x[0]
            return True
        except Exception:
            return False
    return False

耗时 8 秒。应该相当于上面的函数,所以,除非您期望带有元类的类__iter__,并且您不介意裸异常,否则这似乎是判断对象是否可迭代的最佳方法。


奖金:

def super_slow_but_is_almost_definitely_iterable(x):
    try:
        for i,v in enumerate(x):
            if i == 1000:break
        return True
    except Exception: return False

令人惊讶的是,只花了 40 秒。这是最可靠的方法,但它涉及迭代,因此在某些极端情况下会弄乱内部状态。此外,如果您有迭代成本高昂的对象,这将变得非常慢。

解决方案 25:

也许只是写hasattr(obj, "__iter__")

或者...类似这样的方法可能会有效:

def is_iterable(obj: object) -> bool:
    return hasattr(obj, "__iter__")  
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用