代码之家  ›  专栏  ›  技术社区  ›  willem

在Python中,如何确定一个对象是否是不可访问的?

  •  888
  • willem  · 技术社区  · 15 年前

    有类似的方法吗 isiterable ?到目前为止我找到的唯一解决办法是打电话给

    hasattr(myObj, '__iter__')
    

    但我不确定这是多么愚蠢的证明。

    20 回复  |  直到 6 年前
        1
  •  712
  •   Neil G    6 年前
    1. 检查 __iter__ 在序列类型上工作,但会在字符串上失败 在Python 2中 . 我也希望知道正确的答案,直到那时,这里有一种可能性(也适用于字符串):

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

      这个 iter 内置检查 γ迭代 方法或在字符串的情况下 __getitem__ 方法。

    2. 另一个一般的方法是假设一个可重复的,如果它不能在给定的对象上工作,那么就优雅地失败。python词汇表:

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

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

      from collections.abc import Iterable
      
      if isinstance(e, Iterable):
          # e is iterable
      

      但是,这不会检查通过 第二章 .

        2
  •  519
  •   Georg Schölly Crazy Developer    6 年前

    鸭子打字

    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) .

        3
  •  79
  •   timgeb    9 年前

    我想多了解一下 iter , __iter__ __getitem__ 窗帘后面会发生什么。有了这些知识,你就能理解为什么你能做到最好

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

    我将先列出事实,然后再跟进,快速提醒您在使用 for 在python中循环,然后进行讨论以说明事实。

    事实

    1. 您可以从任何对象获取迭代器 o 通过呼叫 iter(o) 如果以下至少一个条件成立:

      a) o 有一个 _ ITER公司__ 返回迭代器对象的方法。迭代器是具有 γ迭代 和A __next__ (蟒蛇2: next 方法。

      b) o 有一个 第二章 方法。

    2. 正在检查的实例 Iterable Sequence 或检查 属性 γ迭代 是不够的。

    3. 如果对象 o 仅实现 第二章 ,但不是 γ迭代 ,请 ITER(O) 意志构建 尝试从中提取项的迭代器 o 按整数索引,从索引0开始。迭代器将捕获 IndexError (但没有其他错误)引发然后引发 StopIteration 本身。

    4. 在最一般的意义上,无法检查迭代器是否由 伊特尔 除了尝试以外,还很理智。

    5. 如果对象 o 器具 _ ITER公司__ , the 伊特尔 功能将确保 返回的对象 γ迭代 是迭代器。没有健康检查 如果一个对象只实现 第二章 .

    6. γ迭代 胜利。如果对象 o 实现两者 γ迭代 第二章 , ITER(O) 将呼叫 γ迭代 .

    7. 如果要使自己的对象不可访问,请始终实现 γ迭代 方法。

    对于 循环

    为了跟进,您需要了解当您使用 对于 在python中循环。如果你已经知道了,可以直接跳到下一节。

    当你使用 for item in o 对于某些无法识别的对象 o Python调用 ITER(O) 需要一个迭代器对象作为返回值。迭代器是实现 α·NEXTXY (或) 下一个 在python 2)方法和 γ迭代 方法。

    按照惯例, γ迭代 迭代器的方法应该返回对象本身(即 return self )然后python调用 下一个 在迭代器上直到 停止迭代 提高了。所有这些都是隐式发生的,但下面的演示使其可见:

    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 将返回一个没有任何问题的迭代器,因为 基本可迭代的 器具 第二章 .

    >>> b = BasicIterable()
    >>> iter(b)
    <iterator object at 0x7f1ab216e320>
    

    但是,需要注意的是 b 没有 _ ITER公司__ 属性,不被视为 可迭代的 顺序 :

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

    这就是为什么 Fluent Python Luciano Ramalho建议 伊特尔 以及处理潜力 TypeError 作为最准确的方法来检查一个对象是否是不可测的。直接从书中引用:

    从python 3.4开始,检查对象 x 是可以调用的 iter(x) 并处理 类型错误 如果不是_t,则为例外。这比使用 isinstance(x, abc.Iterable) ,因为 ITER(X) 同时考虑遗产 第二章 方法,而 可迭代的 ABC没有。

    在第3点:迭代只提供 第二章 ,但不是 γ迭代

    迭代的实例 基本的 按预期工作:python 构造一个迭代器,该迭代器尝试按索引提取项,从零开始,直到 索引错误 提高了。演示对象的 第二章 方法只返回 item 作为参数提供给 __getitem__(self, item) 由迭代器返回 伊特尔 .

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

    请注意,迭代器引发 停止迭代 当它不能返回下一个项目时, 索引错误 是为了 item == 3 内部处理。这就是为什么循环 基本可迭代的 用一个 对于 循环按预期工作:

    >>> for x in b:
    ...     print(x)
    ...
    0
    1
    2
    

    下面是另一个示例,目的是让我们了解迭代器如何返回 ITER公司 尝试按索引访问项目。 WrappedDict 不从继承 dict ,这意味着实例将没有 γ迭代 方法。

    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
    

    请注意,调用 第二章 委托给 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) 为对象调用 o , 伊特尔 将确保返回值 γ迭代 如果方法存在,则为迭代器。这意味着返回的对象 必须实现 α·NEXTXY (或 下一个 在python 2)和 _ ITER公司__ . 伊特尔 无法对仅 提供 第二章 ,因为它无法检查整型索引是否可以访问对象的项。

    class FailIterIterable(object):
        def __iter__(self):
            return object() # not an iterator
    
    class FailGetitemIterable(object):
        def __getitem__(self, item):
            raise Exception
    

    请注意,从中构造迭代器 FailIterIterable 实例在从中构造迭代器时立即失败 FailGetItemIterable 成功,但将在第一次调用时引发异常 α·NEXTXY .

    >>> 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: γ迭代

    这个很简单。如果对象实现 γ迭代 第二章 , 伊特尔 将呼叫 γ迭代 . 考虑下节课

    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点上:您的iterable类应该实现 γ迭代

    你可能会问自己,为什么大多数内置序列 list 实现一个 γ迭代 方法时 第二章 就足够了。

    class WrappedList(object): # note: no inheritance from list!
        def __init__(self, lst):
            self._list = lst
    
        def __getitem__(self, item):
            return self._list[item]
    

    毕竟,在上面类的实例上迭代,委托调用 第二章 list.__getitem__ (使用方括号符号),可以很好地工作:

    >>> wl = WrappedList(['A', 'B', 'C'])
    >>> for x in wl:
    ...     print(x)
    ... 
    A
    B
    C
    

    自定义iterables应该实现的原因 γ迭代 如下:

    1. 如果你实施 γ迭代 ,实例将被视为iterables,并且 isinstance(o, collections.Iterable) 将返回 True .
    2. 如果返回的对象 γ迭代 不是迭代器, 伊特尔 将立即失败并引发 类型错误 .
    3. 特殊处理 第二章 存在的原因是向后兼容。从Fluent python中再次引用:

    这就是为什么任何一个Python序列都是可迭代的:它们都实现 _获取项目__ . 事实上, 标准序列也实现 _ ITER公司__ 你也应该这样,因为 特殊处理 第二章 存在的原因是向后兼容,可能是 以后就不见了(尽管在我写这篇文章的时候它并没有被否决)。

        4
  •  30
  •   Mad Physicist    8 年前

    这不够:返回的对象 __iter__ 必须实现迭代协议(即 next 方法)。请参见 documentation .

    在Python中,一个好的实践是“尝试查看”而不是“检查”。

        5
  •  20
  •   badp    15 年前
    try:
      #treat object as iterable
    except TypeError, e:
      #object is not actually iterable
    

    不运行检查以查看 如果你的鸭子真的是一只鸭子 要想知道它是否是无可挽回的,就把它当作是,如果不是,就抱怨。

        6
  •  18
  •   Peter Mortensen icecrime    8 年前

    在python<=2.5中,您不能也不应该——iterable是一个“非正式”接口。

    但是,由于python 2.6和3.0,您可以利用新的ABC(抽象基类)基础设施以及一些内置的ABC,这些ABC在collections模块中可用:

    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对象注册为可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
    

    如果对象不满足您的期望,您只会抛出一个类型错误,但是如果注册了正确的ABC,您的检查将无效。相反,如果 __iter__ 方法是可用的,python将自动识别该类的对象为可Iterable。

    所以,如果您只是期望一个iterable,那么就重复它,然后忘记它。另一方面,如果您需要根据输入类型做不同的事情,您可能会发现ABC基础结构非常有用。

        7
  •  17
  •   totallyhuman Vlad    7 年前

    迄今为止我找到的最佳解决方案是:

    hasattr(obj, '__contains__')

    它基本上检查对象是否实现 in 操作员。

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

    • 它是一个表达式(作为 兰姆达 ,而不是 尝试……除外 变型)
    • 它是(应该)由所有ITerables实现的,包括 (而不是 __iter__ )
    • 适用于任何python>=2.5

    笔记:

    • python的“请求原谅,而不是允许”的哲学在列表中没有很好的效果,比如你同时拥有iterables和non iterables,你需要根据元素的类型对每个元素进行不同的处理(在try中处理iterables,在except中不处理iterables 工作,但看起来很难看和误导人)
    • 这个问题的解决方案试图在对象上进行实际迭代(例如,obj中的x代表x),以检查它是否是不可迭代的,这可能会对大型iterable s(特别是如果您只需要iterable s的前几个元素,例如)造成显著的性能损失,应该避免。
        8
  •  13
  •   Peter Mortensen icecrime    8 年前

    我找到了一个很好的解决办法 here :

    isiterable = lambda obj: isinstance(obj, basestring) \
        or getattr(obj, '__iter__', False)
    
        9
  •  12
  •   Chris Lutz    15 年前

    你可以试试这个:

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

    如果我们可以生成一个迭代它的生成器(但不要使用生成器,这样它就不会占用空间),那么它是不可迭代的。好像是“该死”的东西。为什么您首先需要确定一个变量是否是可重复的?

        10
  •  10
  •   Anaphory    10 年前

    根据 Python 2 Glossary 迭代是

    所有序列类型(例如 list , str tuple )还有一些非序列类型,比如 dict file 以及用 __iter__() __getitem__() 方法。iterables可以用于for循环和许多需要序列的其他地方(zip(),map(),…)。当iterable对象作为参数传递给内置函数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__") . 你需要检查这两者,因为 STR 没有一个 __iter__ 方法(至少不在python 2中,在python 3中是这样),因为 generator 对象没有 __getitem__ 方法。

        11
  •  5
  •   fmonegaglia    11 年前

    在我的脚本中,我经常发现定义 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,可以简单地实现此代码或上面的代码。

        12
  •  5
  •   MSeifert    8 年前

    具有这样的内置功能:

    from pandas.util.testing import isiterable
    
        13
  •  5
  •   Rotareti    7 年前

    自从 Python 3.5 你可以使用 typing 标准库中与类型相关的内容的模块:

    from typing import Iterable
    
    ...
    
    if isinstance(my_item, Iterable):
        print(True)
    
        14
  •  3
  •   Bob Stein    7 年前
    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()将对类型的字符串说Yes bytes bytearray .

    • 字节 python 3中的对象是不可迭代的 True == is_iterable(b"string") == is_iterable("string".encode('utf-8')) 在python 2中没有这样的类型。
    • 字节射线 python 2和3中的对象是不可迭代的 True == is_iterable(bytearray(b"abc"))

    门诊部 hasattr(x, '__iter__') 方法将对python 3中的字符串说“是”,对python 2中的字符串说“否”(无论 '' b'' u'' )。感谢@luismasuelli注意到它也会让你失望 __iter__ .

        15
  •  2
  •   Peter Mortensen icecrime    8 年前

    最简单的方法,尊重巨蟒的 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())
    

    笔记 :

    1. 区分对象是不可重复的,还是错误的,都无关紧要。 __iter__ 已经实现,如果异常类型相同:无论如何,您将无法迭代该对象。
    2. 我想我理解你的担心:如何 callable 作为一个检查是否我也可以依靠鸭子打字来提高 AttributeError 如果 __call__ 不是为我的对象定义的,但这不是ITerable检查的情况?

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

        16
  •  2
  •   Nomad    7 年前

    这个 isiterable 以下代码返回func 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
    
        17
  •  1
  •   DarthCadeus    6 年前

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

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

    由于明显的原因,没有一个iterable对象不会实现这一点。但是,它不捕获不实现它的用户定义的iterables,也不捕获生成器表达式,后者 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
    

    ---接受乌德米尔的回答

    (这有助于检查您是否可以打电话 len 但是在对象上。)

        18
  •  1
  •   Tcll    6 年前

    我一直不明白为什么蟒蛇 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__ .

    大多数无法识别的对象应该依赖 γ迭代 在特殊情况下,对象返回到 第二章 ,尽管这两者都是对象不可重复的必需条件。
    (由于这是标准的,它也会影响C对象)

        19
  •  0
  •   Jan Musil    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
    
        20
  •  -2
  •   prudhvi Indana    7 年前

    除了常规的尝试和例外,你可以运行帮助。

    temp= [1,2,3,4]
    help(temp)
    

    帮助将提供可以在该对象上运行的所有方法(它可以是任何对象,也可能不是示例中的列表),在本例中是temp。

    注意:这将是您手动执行的操作。