找出是否可以使用`in`运算符

时间:2015-05-21 00:20:10

标签: python iterator containers

找出in运算符是否可以在python中使用,最简单(也是最优雅)的方法是什么? 如果我打开一个python shell并输入:

"" in 2

打印:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: argument of type 'int' is not iterable

根据python-docs,可迭代的是:

  

容器。的 ITER ()

     

返回一个迭代器对象。该对象需要支持下面描述的迭代器协议。如果容器支持不同   在迭代类型中,可以具体提供其他方法   为这些迭代类型请求迭代器。 (对象的一个​​例子   支持多种迭代形式的是树结构   支持广度优先和深度优先遍历。)此方法   对应于Python类型结构的tp_iter槽   Python / C API中的对象。

所以

hasattr([], "__iter__") and hasattr({}, "__iter__")

按预期返回true,但

hasattr("test_string", "__iter__")

返回false。但我可以使用

"test" in "test_string"

没有任何问题。

优雅我指的是不使用try-except解决方案

4 个答案:

答案 0 :(得分:9)

迭代器协议实际上并不需要支持__iter__的类型。它需要一个 支持__iter____getitem__的类型,其序列整数参数从0开始。有关此问题的最佳解释,请参阅iter函数。文档。

因此,如果测试某些东西是否可迭代,hasattr(x, "__iter__")会给你假阴性。

那么 你怎么能这样做呢?好吧,即使你不喜欢它,正确的方法是:

try:
    i = iter(x)
except TypeError:
    # not iterable

另请注意,正如hasattr的文档解释:

  

这是通过调用getattr(object, name)并查看是否引发异常来实现的。

所以,实际上,你根本就没有避免例外;你只是想出一个更复杂的方式来提出异常并将这个事实隐藏起来。

但与此同时,迭代首先是一个红色的鲱鱼。 in运算符使用__contains__方法实现。没有定义__contains__方法的容器类型将回退到迭代和比较,但类型不是必需来实现它办法。您可以__contains__比迭代更快(dictset);你甚至可以成为一个不可迭代的容器。 (请注意,collections module ABCs具有单独的ContainerIterable基础;两者都不依赖于另一个。)

所以,如果你真的想在没有任何异常处理的情况下这样做,你怎么能这样做?

嗯,你必须检查至少有以下一种情况:

  • x__contains__方法。
  • x__iter__方法。
  • x有一个__getitem__方法,当使用数字0调用时,可以成功返回或提升IndexError

即使您接受最后一个可能无法在没有实际尝试使用数字0进行测试的情况下进行测试,并且只是假设__getitem__是&#34;足够接近& #34;,如何在不依赖异常的情况下对此进行测试?

你真的不能。例如,您可以迭代dir(x),但这对于动态定义__contains__的类不起作用,例如,在委托给__getattr__的{​​{1}}方法中

而且,即使你可以,如果你有一个将self.real_sequence定义为不参数的类,会发生什么?该属性存在,但__contains__仍然会引发in

所有这些都忽略了which special methods are looked up on the object and which on the type itself上的(依赖于实现的)规则。例如,在CPython 2.7中:

TypeError

答案 1 :(得分:7)

尝试除了正确而优雅的方式。

首先,a in b是否会引发异常取决于两者 a和b,而不仅仅是b。

另一个问题是in的工作方式有多种。以下是支持in但不支持迭代的对象示例:

>>> class EvenNumbers(object):
...     def __contains__(self, n):
...         return n % 2 == 0
...     
>>> even_numbers = EvenNumbers()
>>> 4 in even_numbers
True
>>> 5 in even_numbers
False
>>> for even_number in even_numbers:
...     pass
... 
TypeError: 'EvenNumbers' object is not iterable

以下是支持迭代的对象示例,但未定义__contains__

>>> import itertools as it
>>> even_numbers = (2*n for n in it.count())
>>> 4 in even_numbers
True
>>> even_numbers.__contains__
AttributeError: 'generator' object has no attribute '__contains__'

因此,为了实现有效的LBYL实现,您必须考虑a in b可以工作(或不工作)的每种可能方法。我这里只列出了一对,还有其他几个。你会发现你的代码变得非常冗长和丑陋,最终会意识到try / except一直是阻力最小的路径!

答案 2 :(得分:2)

in(和not in)运算符使用__contains__()来检查成员资格(通常为)。检查此问题的最简单和最优雅的方法是直接存在__contains__属性,或者用于Container抽象基类:

hasattr(obj, '__contains__')

# Python 3: Container
import collections.abc
isinstance(obj, collections.abc.Container)

# Python 2: Container
import collections
isinstance(obj, collections.Container)

但是,文档确实提到了:

  

对于未定义__contains__()的对象,成员资格测试首先通过__iter__()尝试迭代,然后通过__getitem__()尝试旧的序列迭代协议,请参阅this section in the language reference。< / p>

因此,如果您希望绝对确定in可以在不依赖try-except块的情况下使用,则应使用:

hasattr(obj, '__contains__') or hasattr(obj, '__iter__') or hasattr(obj, '__getitem__')

如果您只期望container个类似的对象,我会忽略__iter__()甚至__getitem__(),只需坚持__contains__()

答案 3 :(得分:1)

您可以查看是否'__contains__' in dir(theobject),但我并不认为try不优雅。