44
votes

I have a series of Python classes in a file. Some classes reference others.

My code is something like this:

class A():
    pass

class B():
    c = C()

class C():
    pass

Trying to run that, I get NameError: name 'C' is not defined. Fair enough, but is there any way to make it work, or do I have to manually re-order my classes to accommodate? In C++, I can create a class prototype. Does Python have an equivalent?

(I'm actually playing with Django models, but I tried not complicate matters).

6
It's called function prototype in Kernighan and Ritchie, where I remember it from. - Mat
Just checked, no "class prototypes" in my K&R copy ;) - Constantin
Yeah its a bit confusing because the concept of prototypes in OOP and Functional coding are completely unrelated. o_O - Shayne
This issue is cropping up more with type tags (the return types of methods need to be declared in advance). Looks like the path of least resistance is to define types bottom up, even though such an arrangement is not the most readable layout. - Dilum Ranatunga

6 Answers

37
votes

In Python you don't create a prototype per se, but you do need to understand the difference between "class attributes" and instance-level attributes. In the example you've shown above, you are declaring a class attribute on class B, not an instance-level attribute.

This is what you are looking for:

class B():
    def __init__(self):
        self.c = C()
64
votes

Actually, all of the above are great observations about Python, but none of them will solve your problem.

Django needs to introspect stuff.

The right way to do what you want is the following:

class Car(models.Model):
    manufacturer = models.ForeignKey('Manufacturer')
    # ...

class Manufacturer(models.Model):
    # ...

Note the use of the class name as a string rather than the literal class reference. Django offers this alternative to deal with exactly the problem that Python doesn't provide forward declarations.

This question reminds me of the classic support question that you should always ask any customer with an issue: "What are you really trying to do?"

8
votes

This would solve your problem as presented (but I think you are really looking for an instance attribute as jholloway7 responded):

class A:
    pass

class B:
    pass

class C:
    pass

B.c = C()
4
votes

Python doesn't have prototypes or Ruby-style open classes. But if you really need them, you can write a metaclass that overloads new so that it does a lookup in the current namespace to see if the class already exists, and if it does returns the existing type object rather than creating a new one. I did something like this on a ORM I write a while back and it's worked very well.

1
votes

A decade after the question is asked, I have encountered the same problem. While people suggest that the referencing should be done inside the init method, there are times when you need to access the data as a "class attribute" before the class is actually instantiated. For that reason, I have come up with a simple solution using a descriptor.

class A():
    pass

class B():
    class D(object):
        def __init__(self):
            self.c = None
        def __get__(self, instance, owner):
            if not self.c:
                self.c = C()
            return self.c
    c = D()

class C():
    pass

>>> B.c
>>> <__main__.C object at 0x10cc385f8>
0
votes

All correct answers about class vs instance attributes. However, the reason you have an error is just the order of defining your classes. Of course class C has not yet been defined (as class-level code is executed immediately on import):

class A():
    pass

class C():
    pass

class B():
    c = C()

Will work.