Reputation: 2175
Short version of the question: Why is the type of an instance of a generator not the generator function that created the instance? That is, if we have a generator, say def G(): yield 1
and g
is a generator instance created by G
, that is g = G()
, then why is type(g) is G
false?
Details:
I recently asked a question on how to determine what type of generator a given generator object is. (See In Python, is there any way to test a generator object to find out which generator created it?.) The answer ended up requiring the use of __name__
. However, this still left a question unanswered for me: why are generators not types in Python?
All the code below is with Python 3.3 in mind, but I believe most of it is applicable to older versions of Python (at least 2.7 anyway) and newer versions, namely 3.4.
Let's look at a very simple example of an iterator implemented as a class first:
class C(object):
def __iter__(self):
return self
def __next__(self):
return 1
c = C()
print(next(c)) # 1
print(type(c)) # <class '__main__.C'>
print(type(c) is C) # True
Functionally, the class above is the same as the generator given below. (Of course, we are missing throw
, send
and close
, and there are other subtle differences, but I believe they are not that relevant to the question at hand.)
def G():
while True:
yield 1
g = G()
print(next(g)) # 1
print(type(g)) # <class 'generator'>
print(type(g) is G) # False
But as you can see, the types of objects created by the generator defined using def
and yield
and an iterator created using a class are handled completely differently. All generator instances are given the generic type generator
. This, to me, is analogous to all objects created by classes being given a generic type, say object
, which of course is not the case.
Python is overall is a very consistent and logical language. In particular, Python's treatment of types is very consistent: the "creator" of an object becomes its type. This works quite nicely even with metaclasses:
class M(type):
def __new__(cls, *args):
return super().__new__(cls, *args)
class C(metaclass=M):
pass
c = C()
print(type(c) is C) # True
print(type(C) is M) # True
print(type(M) is type) # True
print(type(type) is type) # True
You can see here that type(c)
is C
because c
is created by C
, and type(C)
is M
because the class object C
was created by the metaclass M
, and finally M
itself is a class object which is created by the metaclass type
. We even have type(type)
is type
, which, being a self-reference, provides a root for the type hierarchy.
However, with generators, I feel like this consistency is broken. In the above, when we have g = G()
, the object g
is in fact created by G
. Shouldn't it have its type set to be equal to G
? Or is there a deeper reason behind the choice of a generic type for all generator instances that I am failing to see?
Upvotes: 3
Views: 264
Reputation: 97331
The short answer is function object is not a type, a function has no relation with the object it returned.
To check if the generator is created by a function, you can compare the code object:
def f():
yield 1
def g():
yield 1
t = f()
def is_generator_created_by(g, func):
return g.gi_code is func.func_code
print is_generator_created_by(t, f), is_generator_created_by(t, g)
Upvotes: 0
Reputation: 32449
In the above, when we have g = G(), the object g is in fact created by G. Shouldn't it have its type set to be equal to G?
If type(g)
were indeed G
, then in the following example type(f)
would be F
and not int
.
def F(): return 42
f = F()
Or do I misunderstand your point?
G
is a function, not a class, i.e. not an object of type <class 'type'>
.
Upvotes: 4
Reputation: 281604
In particular, Python's treatment of types is very consistent: the "creator" of an object becomes its type.
This is only true for classes. Not everything that creates an object is a class:
type(open('whatever')) is not open
type(iter(whatever)) is not iter
type(compile('whatever')) is not compile
A generator function is a function, not a class. You cannot subclass it, or define methods on it, or make it inherit from something, or change its metaclass, or do almost anything you could do with a real class. Learning the function that created a generator does not change how you interact with it in the same way that learning the class of an object changes how you interact with the object. Unlike with different classes, no matter what function created a generator, it provides the exact same API.
In short, there is no benefit to making generator functions types instead of functions.
Upvotes: 5