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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With