Does enumerate() produce a generator object?

前端 未结 5 926
温柔的废话
温柔的废话 2020-12-06 03:55

As a complete Python newbie, it certainly looks that way. Running the following...

x = enumerate([\'fee\', \'fie\', \'foe\'])
x.next()
# Out[1]: (0, \'fee\')         


        
5条回答
  •  广开言路
    2020-12-06 04:46

    Testing for enumerate types:

    I would include this important test in an exploration of the enumerate type and how it fits into the Python language:

    >>> import collections
    >>> e = enumerate('abc')
    >>> isinstance(e, enumerate)
    True
    >>> isinstance(e, collections.Iterable)
    True
    >>> isinstance(e, collections.Iterator)
    True
    

    But we see that:

    >>> import types
    >>> isinstance(e, types.GeneratorType)
    False
    

    So we know that enumerate objects are not generators.

    The Source:

    In the source, we can see that the enumerate object (PyEnum_Type) that iteratively returns the tuple, and in the ABC module we can see that any item with a next and __iter__ method (actually, attribute) is defined to be an iterator. (__next__ in Python 3.)

    The Standard Library Test

    So the Abstract Base Class library uses the following test:

    >>> hasattr(e, 'next') and hasattr(e, '__iter__')
    True
    

    So we know that enumerate types are iterators. But we see that a Generator type is created by a function with yield in the documentation or a generator expression. So generators are iterators, because they have the next and __iter__ methods, but not all iterators are necessarily generators (the interface which requires send, close, and throw), as we've seen with this enumerate object.

    So what do we know about enumerate?

    From the docs and the source, we know that enumerate returns an enumerate object, and we know by definition that it is an iterator, even if our testing states that it is explicitly not a generator.

    We also know from the documentation that generator types simply "provide a convenient way to implement the iterator protocol." Therefore, generators are a subset of iterators. Furthermore, this allows us to derive the following generalization:

    All generators are iterators, but not all iterators are generators.

    So while we can make our enumerate object into a generator:

    >>> g = (i for i in e)
    >>> isinstance(g, types.GeneratorType)
    True
    

    We can't expect that it is a generator itself, so this would be the wrong test.

    So What to Test?

    And what this means is that you should not be testing for a generator, and you should probably use the first of the tests I provided, and not reimplement the Standard Library (which I hope I can be excused from doing today.):

    If you require an enumerate type, you'll probably want to allow for iterables or iterators of tuples with integer indexes, and the following will return True:

    isinstance(g, collections.Iterable)
    

    If you only want specifically an enumerate type:

    isinstance(e, enumerate)
    

    PS In case you're interested, here's the source implementation of generators: https://github.com/python/cpython/blob/master/Objects/genobject.c
    And here's the Generator Abstract Base Class (ABC): https://github.com/python/cpython/blob/master/Lib/_collections_abc.py#L309

提交回复
热议问题