问题
First I want to clarify, I'm NOT asking what is "iterator".
This is how the term "iterable" is defined in Python's doc:
iterable
An object capable of returning its members one at a time. Examples of iterables include all sequence types (such as list, str, and tuple) and some non-sequence types like dict, file objects, and objects of any classes you define with an __iter__() or __getitem__() method.
Iterables can be used in a for loop and in many other places where a sequence is needed (zip(), map(), ...). When an iterable object is passed as an argument to the built-in function iter(), it returns an iterator for the object. This iterator is good for one pass over the set of values. When using iterables, it is usually not necessary to call iter() or deal with iterator objects yourself. The for statement does that automatically for you, creating a temporary unnamed variable to hold the iterator for the duration of the loop.
See also iterator, sequence, and generator.
As other people suggested, using isinstance(e, collections.Iterable)
is the most pythonic way to check if an object is iterable.
So I did some test with Python 3.4.3:
from collections.abc import Iterable
class MyTrain:
def __getitem__(self, index):
if index > 3:
raise IndexError("that's enough!")
return index
for name in MyTrain():
print(name) # 0, 1, 2, 3
print(isinstance(MyTrain(), Iterable)) # False
The result is quite strange: MyTrain
has defined __getitem__
method, but it is not considered as an iterable object, not to mention it's capable of returning one number at a time.
Then I removed __getitem__
and added the __iter__
method:
from collections.abc import Iterable
class MyTrain:
def __iter__(self):
print("__iter__ called")
pass
print(isinstance(MyTrain(), Iterable)) # True
for name in MyTrain():
print(name) # TypeError: iter() returned non-iterator of type 'NoneType'
It is now considered as a "true" iterable object in spite of it cannot produce anything while iterating.
So did I misunderstand something or is the documentation incorrect?
回答1:
I think the point of confusion here is that, although implementing __getitem__
does allow you to iterate over an object, it isn't part of the interface defined by Iterable.
The abstract base classes allow a form of virtual subclassing, where classes that implement the specified methods (in the case of Iterable
, only __iter__
) are considered by isinstance
and issubclass
to be subclasses of the ABCs even if they don't explicitly inherit from them. It doesn't check whether the method implementation actually works, though, just whether or not it's provided.
For more information, see PEP-3119, which introduced ABCs.
using
isinstance(e, collections.Iterable)
is the most pythonic way to check if an object is iterable
I disagree; I would use duck-typing and just attempt to iterate over the object. If the object isn't iterable a TypeError
will be raised, which you can catch in your function if you want to deal with non-iterable inputs, or allow to percolate up to the caller if not. This completely side-steps how the object has decided to implement iteration, and just finds out whether or not it does at the most appropriate time.
To add a little more, I think the docs you've quoted are slightly misleading. To quote the iter docs, which perhaps clear this up:
object must be a collection object which supports the iteration protocol (the
__iter__()
method), or it must support the sequence protocol (the__getitem__()
method with integer arguments starting at0
).
This makes it clear that, although both protocols make the object iterable, only one is the actual "iteration protocol", and it is this that isinstance(thing, Iterable)
tests for. Therefore we could conclude that one way to check for "things you can iterate over" in the most general case would be:
isinstance(thing, (Iterable, Sequence))
although this does also require you to implement __len__
along with __getitem__
to "virtually sub-class" Sequence
.
回答2:
It is an iterable. However you haven't inherited from abc.Iterable
, so naturally Python won't report it as being descended from that class. The two things -being an iterable, and descending from that base class - are quite separate.
回答3:
Iterable
is something(collection anything) that allows some kind of iteration on its elements. But what is the generic way of iteration in python? That is using - in
keyword, which uses __iter__
method of an object. So, in that terms any object that defines __iter__
can be used with in
, and is an Iterable.
So, most 'duck-typish' way to check if an object is iterable is if an object is this, (Yeah, I know implicitly that is what's happening in isinstance
case as well, due to virtual classes)
hasattr(train, '__iter__')
because according to duck-typing, we care about behavior provided by an object instead of its ancestory.
If you have a faulty implementation of __iter__
that doesn't means object is not iterable, it just means that you have bug in your code.
Note:- Those objects that don't define __iter__
can still be iterable in general sense, by using some other method, it's just they can't be used with in
keyword.
E.g.:- NumberList
instance is iterable over each
method, but isn't iterable in python sense.
class NumberList:
def __init__(self, values):
self.values = values
def each(self):
return self.values
来源:https://stackoverflow.com/questions/32799980/what-exactly-does-iterable-mean-in-python-why-isnt-my-object-which-implement