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