Sahand
Sahand

Reputation: 2175

Why are Python's generators not types?

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

Answers (3)

HYRY
HYRY

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

Hyperboreus
Hyperboreus

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

user2357112
user2357112

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

Related Questions