2

What is the simplest (and most elegant) way, to find out if the in operator can be used in python? If I open a python shell and type in:

"" in 2

it prints:

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

according to the python-docs an iterable is:

container.iter()

Return an iterator object. The object is required to support the iterator protocol described below. If a container supports different types of iteration, additional methods can be provided to specifically request iterators for those iteration types. (An example of an object supporting multiple forms of iteration would be a tree structure which supports both breadth-first and depth-first traversal.) This method corresponds to the tp_iter slot of the type structure for Python objects in the Python/C API.

so

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

return true as expected, but

hasattr("test_string", "__iter__")

returns false. But I can use

"test" in "test_string"

without any problems.

By elegant I refer to NOT use a try-except solution

Rafael T
  • 15,401
  • 15
  • 83
  • 144
  • 6
    Aside: if you think of try-except as inelegant, you're going to find Python very frustrating. (Your code is also probably going have race conditions all over the place, but that's another issue.) – DSM May 21 '15 at 00:22
  • A problem with a dynamically typed language is indeed that the compiler can not check this at compile time (of course you buy something in return). – Willem Van Onsem May 21 '15 at 00:24
  • [In Python, how do I determine if an object is iterable?](http://stackoverflow.com/questions/1952464/in-python-how-do-i-determine-if-an-object-is-iterable) – ahmed May 21 '15 at 00:25
  • You might also check for `__contains__` – inspectorG4dget May 21 '15 at 00:32
  • `hasattr()` is just `getattr()` behind a try/except. Trying to avoid exceptions is really not worth the trouble. – Kevin May 21 '15 at 00:46
  • It's worth noting that, as of Python 3.2 (I think), all of the built-in sequence-like types, including `str`, support the complete `Sequence` API, so `hasattr("test string", "__iter__")` is actually `True`. But that doesn't help you in 2.7. Or with third-party types. – abarnert May 21 '15 at 00:51

4 Answers4

9

The iterator protocol doesn't actually require a type to support __iter__. It requires a type to either support __iter__, or __getitem__ with sequential integer arguments starting from 0. See the iter function for the best explanation of this in the docs.

So, hasattr(x, "__iter__") will give you false negatives if testing whether something is iterable.

So, how can you do this? Well, the right way, even if you don't like it, is:

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

Also, note that, as the docs for hasattr explain:

This is implemented by calling getattr(object, name) and seeing whether it raises an exception or not.

So, really, you're not avoiding exceptions at all; you're just coming up with a more convoluted way to raise an exception and hide that fact from yourself.


But meanwhile, iteration is a red herring in the first place. The in operator is implemented with the __contains__ method. Container types that don't define a __contains__ method will fall back to iterating and comparing, but types aren't required to implement it that way. You can have a __contains__ that's much faster than iterating could be (as with dict and set); you can even be a container without being an iterable. (Note that the collections module ABCs have separate Container and Iterable bases; neither one depends on the other.)


So, if you really wanted to do this without any exception handling, how could you?

Well, you have to check that at least one of the following is true:

  • x has a __contains__ method.
  • x has an __iter__ method.
  • x has a __getitem__ method that, when called with the number 0, either returns successfully or raises IndexError.

Even if you accept that the last one can't possibly be tested without actually trying to call it with the number 0 and just assume that having __getitem__ is "close enough", how can you test for this without relying on exceptions?

You really can't. You could, e.g., iterate over dir(x), but that won't work for classes that define __contains__ dynamically, e.g., in a __getattr__ method that delegates to self.real_sequence.

And, even if you could, what happens if you have, say, a class that defines __contains__ as taking no arguments? The attribute is there, but in is still going to raise a TypeError.

And all of this is ignoring the (implementation-dependent) rules on which special methods are looked up on the object and which on the type itself. For example, in CPython 2.7:

>>> class C(object): pass
>>> c = C()
>>> c.__contains__ = lambda self, x: return True
>>> hasattr(c, '__contains__')
True
>>> c.__contains__(2)
True
>>> 2 in c
TypeError: argument of type 'C' is not iterable
abarnert
  • 354,177
  • 51
  • 601
  • 671
  • That last one is interesting. Irrespective of cpython, does python specify whether it will/will not look up on the instance or the class? – wim May 21 '15 at 00:58
  • @wim: IIRC, Python 2.x just says that "special methods" "may" be looked up on the class. I believe 3.x limits it to a specific list of special methods, but still leaves it up to the implementation whether or not they are looked up that way. – abarnert May 21 '15 at 01:03
  • @wim: Here we go, [3.4.12 Special method lookup for new-style classes](https://docs.python.org/2/reference/datamodel.html#new-style-special-lookup): "For new-style classes, implicit invocations of special methods are only guaranteed to work correctly if defined on an object’s type, not in the object’s instance dictionary." – abarnert May 21 '15 at 01:04
  • 1
    @wim: It's an implementation-specific optimization, and they don't want to tie the hands of other implementations. – Kevin May 21 '15 at 01:04
  • @wim: And I was wrong about 3.x; [3.3.9 Special method lookup](https://docs.python.org/3/reference/datamodel.html#special-method-lookup) still just says "implicit invocations of special methods", no specific list of them. – abarnert May 21 '15 at 01:08
8

Try except is the correct and elegant way.

First of all, whether a in b will raise exception or not depends on both a and b, not just on b alone.

The other problem is there are multiple ways that in works. Here is an example of an object which supports in, but doesn't support iteration:

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

And here is an example of an object which supports iteration, but doesn't define __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__'

So to have a working LBYL implementation, you will have to take into account every possible method in which a in b can work (or not). I have only listed a couple here, there are several others. You will find your code becoming very long and ugly, and will eventually realise that try/except was the path of least resistance all along!

wim
  • 338,267
  • 99
  • 616
  • 750
  • 1
    If I could give you +2 just for the last paragraph, I would. Unfortunately, when I try, StackOverflow seems to raise an exception. :) – abarnert May 21 '15 at 00:56
2

The in (and not in) operator uses __contains__() to check for membership (typically). The simplest and most elegant way to check for this is either the direct existence of the __contains__ attribute, or for the Container abstract base class:

hasattr(obj, '__contains__')

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

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

However, the documentation does mention:

For objects that don’t define __contains__(), the membership test first tries iteration via __iter__(), then the old sequence iteration protocol via __getitem__(), see this section in the language reference.

So, if you want to be absolutely certain in can be used without relying on a try-except block, you should use:

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

If you are expecting only container-like objects though, I would ignore __iter__() and maybe even __getitem__(), and just stick to __contains__().

Uyghur Lives Matter
  • 18,820
  • 42
  • 108
  • 144
  • I'm -0.5 on this: I'd rather use `isinstance(obj, collections.abc.Container)`, but I'm not sure `hasattr()` is actually *wrong*. – Kevin May 21 '15 at 00:27
  • This alone does not determine whether `x in obj` will work on not. – wim May 21 '15 at 00:30
  • 2
    Even checking for both `Iterable` and `Container` (or `hasattr`ing for both `__iter__` and `__contains__`) isn't sufficient; a class with `__getitem__` that returns or raises `IndexError` when called on the number `0` will fails both tests, but will still succeed with `in` (and with `iter`). – abarnert May 21 '15 at 00:36
  • @abarnert: Sequences generally do implement `__iter__`, according to `collections.abc`. If you need to worry about something sufficiently ill-behaved, I'd just use the `try`/`except`. It's far more idiot-proof. – Kevin May 21 '15 at 00:44
  • @Kevin: Something has to have `__iter__` to be a `Sequence`, as defined by the ABCs; it doesn't have to have `__iter__` to meet the sequence protocol (or the iterable protocol).. _Most_ sequence-like classes will, of course, but why do you want a test that works for most cases instead of all? – abarnert May 21 '15 at 00:46
  • 1
    For the latest edit: the simplest and most elegant way to check for this is to use `try:`/`except:`. And it has the nice side benefit of being correct, not just approximately/often correct. – abarnert May 21 '15 at 00:50
  • @abarnert: Agreed, `try`/`except` is far more reasonable, especially since the Python developers could (hypothetically) add more container semantics in the future. – Kevin May 21 '15 at 00:51
  • @Kevin: Notice that the question asks about `str`, and `str` is not guaranteed to be a `Container` in Python 2.7 (it happens to be one in CPython 2.7—but it's not an `Iterable`, as the OP noticed…). And there are plenty of third-party classes beyond the handful of builtins. So it's more than just "hypothetical". – abarnert May 21 '15 at 00:54
  • 1
    @abarnert: For 3.x I'm going to disagree with you. AFAIK all the builtin types now support all the expected methods. User-defined types will (or *should*) inherit from the classes in `collections.abc` because it saves them the trouble of defining all the mixin methods (like `.index()` for sequences). – Kevin May 21 '15 at 00:55
  • @Kevin: I already mentioned that in the comments on the question itself. (I think it's 3.2+, not 3.0+, but nobody should be using 3.0 or 3.1…) But there are actually still plenty of third-party classes that don't use the ABCs. (And even a couple in the stdlib that were missed in the 3.2 cleanup and nobody's bothered to fix…) – abarnert May 21 '15 at 00:57
  • @abarnert I guess it depends what you're aiming for. I would say if you expect `value in obj` to succeed if the object is a container then an exception should be a real error, and a try-except should not be used. However, if you only care about whether the value could be in the object, then the try-except is a better solution. – Uyghur Lives Matter May 21 '15 at 00:57
  • 1
    @cpburnz: You're not thinking in Python terms. The idea that exceptions should only be raised on a "real error" just doesn't fit in with a language where even a `for` loop is a `try` under the covers. Also, if it's a real error, that's exactly when you _don't_ want `except`, so the exception can percolate up to the level where you can handle things appropriately (which may even be all the way up to aborting the program, if it's an unexpected logic error in your code). – abarnert May 21 '15 at 01:00
  • @abarnert Either you misread part of my comment, or I'm not understanding yours. I agree that a real error should not be caught with an `except` until it travels to a level where it is appropriate to handle it. – Uyghur Lives Matter May 21 '15 at 01:04
  • 1
    @cpburnz: Ah, I think I misread. You're saying you should always use an exception, it's just a question of whether you use a (local) `try`/`except` or just let the exception be an exception? In that case, I agree 100%. – abarnert May 21 '15 at 01:09
  • Comments are not for extended discussion; this conversation has been [moved to chat](http://chat.stackoverflow.com/rooms/78366/discussion-on-answer-by-cpburnz-find-out-if-the-in-operator-can-be-used). – Taryn May 21 '15 at 01:15
0

You can check whether '__contains__' in dir(theobject), but I don't really think that try is inelegant.

TigerhawkT3
  • 48,464
  • 6
  • 60
  • 97
  • 1
    Of course that won't work if `theobject`'s type defines `__iter__` but not `__contains__`. Or a valid `__getitem__` but neither. Or if `theobject.__contains__` is stored in the instance dict rather than the type dict. Or if the type doesn't use a `__dict__`, or it does but `__contains__` is generated dynamically through a `__getattr__` or `__getattribute__` method. – abarnert May 21 '15 at 00:45
  • It can work in some cases, but of course `try..except` is the way to go. – TigerhawkT3 May 21 '15 at 00:52
  • 2
    @abarnert: You missed one: The object defines a poorly-implemented `__dir__` and doesn't tell you what it implements correctly. Also, I'm 85% sure you can't dynamically generate special methods even via the metaclass. – Kevin May 21 '15 at 00:59
  • @Kevin: Ooh, I didn't even think of that one, but you're right, you can always fake `__dir__`, and do so maliciously or ineptly. :) – abarnert May 21 '15 at 01:00
  • 1
    Here is another fun one, a call to `__contains__` itself could modify the instance or the class and delete the `__contains__` method :) – wim May 21 '15 at 01:04
  • @wim If someone manipulates objects that way, then he deserves the inconsistency. :) – Uyghur Lives Matter May 21 '15 at 01:06
  • @wim: You can also just make `__contains__` exist but raise an `AttributeError` when called. That fools almost all implicit special-method lookups. Or, for even more fun, go the opposite direction, and have a `__getattribute__` that hides `__getattr__` so you get an `AttributeError` looking for the fallbacks. – abarnert May 21 '15 at 01:10
  • @wim: Or, even better: a `__contains__` that reassigns to `__builtin__.hasattr`. (Or, to break EAFP, `__builtin__.TypeError`.) – abarnert May 21 '15 at 01:12
  • @abarnert I was thinking more of making it work the first time .. but it shoots itself in the foot. This could break even an implementation which *tries to check* using try/except as a means of knowing beforehand. I guess my point is that it's better just to code in an EAFP style in the first place. – wim May 21 '15 at 01:12