I am inspecting type hints such as list[int] which is a GenericAlias.
If I get the origin using typing.get_origin(list[int]) or list[int].__origin__ it returns the class type list, as expected: <class 'list'>
How can I check if the class is iterable without instantiating it, or is that the only way?
The usual iter() and isinstance(object, collections.abc.Iterable) obviously don't work as they expect the instantiated object, not the class.
I saw this answer, but doesn't seem to work correctly in Python 3.10 (even when i_type variable is substituted for t).
This depends a bit on what you define as iterable.
The Collections Abstract Base Classes module considers a class to implement the
Iterableprotocol once it defines the__iter__method. Note that you do not need to define the__next__method. This is only needed, if you want to implement anIterator. (Those two often get confused.)A slightly broader definition in accordance with the the general notion of an iterable in the documentation also includes classes that implement
__getitem__(with integer indexes starting at 0 as Sequences do).In practice this means that you have an iterable class, if and only if you can call the built-in
iter()function with an instance of that class. That function merely calls the instance's__iter__method, if it finds one.If that is what you consider to be iterable as well, the most reliable way to check that I can think of is the following. We first find out if one of the classes in the method resolution order implements the desired instance method:
(Thanks @user2357112 for reminding me of checking inherited methods.)
That first check is self-explanatory; if it fails, we obviously don't have that method. But this is where it gets a little pedantic.
The second check actually does more than one thing. First off it ensures that that the
nameon ourclsis defined as a method, i.e. callable. But it also ensures us against any descriptor shenanigans (to an extent). This is why we checkcallable(cls.__dict__[name])and not simplycallable(getattr(cls, name)).If someone were to (for whatever reason) have a
@classmethodor a@propertycalledname, that would not fly here.Next we write our actual iterable checking function:
There are still a number of pitfalls here though.
A little demo:
The output:
You should immediately notice that my function returns
TrueforBazeven though it clearly messes up and delivers an integer instead of anIterator. This is to demonstrate that the contract of theIterableprotocol ends at the definition of__iter__and does not cover what it returns. Even though one would reasonably assume that it must return anIterator, it is technically still anIterableeven if it doesn't.Another great practical example of this was pointed out by @user2357112: The
numpy.ndarrayis certainly iterable, by contract and in practice in most situations. However, when it is a 0D-array (i.e. a scalar), the__iter__method raises aTypeErrorbecause iterating over a scalar makes little sense.The non-
strictversion of the function is even less practical since a class could easily and sensibly implement__getitem__, but not in the way expected byiter().I see no way around those issues and even the Python documentation tells you that
If it is actually the
Iteratorthat you are interested in, you could of course expand the function to do the same checks done for the__iter__method also for the__next__method. But keep in mind that this will immediately exclude all the built-in collection types likelist,dictetc. because they actually don't implement__next__. Again, referring tocollections.abc, you can see that allCollectionsubtypes only inherit fromIterable, not fromIterator.Hope this helps.