Reputation: 919
Python 3 has introduced generator-like objects to be returned upon calling range()
and zip()
. The object returned acts like a generator and can be iterated through once but doesn't 'print' well, much like the enumerate()
return argument.
I was perplexed to see, however, that they are distinct object types and do not belong to types.GeneratorType
, or at least this is what the types
module shows. A function that would run e.g. expecting a generator would not detect them. What is their inheritance? Do they belong to a main "generator" structure, so that they e.g. could be identified along with other generators?
import types
a = [1,2,3]
b = [4,5,6]
# create some generator-type objects
obj_zip = zip(a,b)
obj_enu = enumerate(a)
obj_r = range(10)
print(type(obj_zip))
print(type(obj_enu))
print(type(obj_r))
# checking against types.GeneratorType returns False
print(isinstance(obj_zip,types.GeneratorType))
print(isinstance(obj_enu,types.GeneratorType))
print(isinstance(obj_r,types.GeneratorType))
# checking against their own distinct object types returns True
print(isinstance(obj_zip,zip))
Upvotes: 2
Views: 473
Reputation: 140276
not meant to be a full answer (ShadowRanger answer already explains everything) but just to state that types.GeneratorType
is really a very limited type as shown in types.py
source:
def _g():
yield 1
GeneratorType = type(_g())
it only scopes the type of generator functions. Other "generator-like" objects don't use yield
so they're not a match.
Upvotes: 0
Reputation: 155536
types.GeneratorType
The type of generator-iterator objects, created by generator functions.
Generator functions are a specific thing in the language; it means functions that use yield
or yield from
(or generator expressions, which are just a shorthand for inline generator functions). It's a subset of the set of iterators (all things that you can call next()
on to get a new value), which is in turn a subset of iterables (all things that you can call iter()
on to get an iterator; iterators themselves are iterables, where iter(iterator)
behaves as the identity function).
Basically, if you're testing for "can I loop over this?", test isinstance(obj, collections.abc.Iterable)
. If you're checking "is this an exhaustible iterator?" (that is, will I exhaust it by looping over it?), test either isinstance(obj, collections.abc.Iterator)
or for the duck-typing based approach, test iter(obj) is obj
(the invariants on iterators require that iter(iterator)
yield the original iterator object unchanged).
Note that range
is not a generator or iterator. Per the docs:
Rather than being a function, range is actually an immutable sequence type, as documented in Ranges and Sequence Types — list, tuple, range.
Being an immutable sequence type means it is an iterable, but that's it. The fact that it is usually used as if it were an iterator is irrelevant; if it were an iterator, the second loop here would never execute:
r = range(3)
for i in r:
print("First", i)
for i in r:
print("Second", i)
but it works just fine, because each (implicit) call to iter(r)
returns a new iterator based on the same underlying iterable.
Upvotes: 5
Reputation: 11561
The documentation says that enumerate is functionally equivalent to a generator. Actually it is implemented in C and returns an iterator, not a generator as described in Does enumerate() produce a generator object. Generators and iterables are almost the same. This is explained in detail in Difference between Python's Generators and Iterators.
I'm assuming you're trying to solve a real problem, like finding out if you can iterate over something. To solve that you can test if something is an instance of collections.Iterable
.
a = enumerate([1,2,3])
isinstance(a, collections.Iterable)
>>> True
Upvotes: 0