51

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).

5
  • 4
    FWIW, it's called en.wikipedia.org/wiki/Forward_declaration, not prototype (en.wikipedia.org/wiki/Prototype-based_programming). Commented Feb 19, 2009 at 8:04
  • 1
    It's called function prototype in Kernighan and Ritchie, where I remember it from. Commented Feb 19, 2009 at 20:40
  • 1
    Just checked, no "class prototypes" in my K&R copy ;) Commented Feb 21, 2009 at 23:55
  • Yeah its a bit confusing because the concept of prototypes in OOP and Functional coding are completely unrelated. o_O Commented Dec 11, 2012 at 10:13
  • 1
    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. Commented Nov 2, 2017 at 19:37

6 Answers 6

69

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?"

Sign up to request clarification or add additional context in comments.

3 Comments

It was clear from the question what the OP was trying to do, that it was Django-related and that the OP was trying to understand Python's model for object-oriented programming, because Django's ORM can be confusing to beginners.
That may be true, but what the OP ended up asking was very different from the title of the question. I'm reluctant to change the title three years later, even though it is inaccurate.
Django specific question: stackoverflow.com/questions/7298326/…
41

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()

12 Comments

I'd be curious for an explanation as to why Python can find the def'n of C when it's assigning an instance attribute but not a class level one. Is it because it's trying to do the assignment at class definition rather than at runtime?
Yes, since the c=C() is in the class definition (executed upon module load), class C does not yet exist.
@truppo is correct. When you declare class attributes the corresponding references are resolved when the module is loaded (i.e. the class is interpreted). The init method is analogous to constructors in other languages so references in its local scope don't have to resolve until invoked
Interesting answer, and I now have better search terms to understand class and instance-level attributes. I'm actually playing with Django models, so am not sure exactly how class versus instance-level attributes will affect that.
Good recommendations here. The only other thing I would add is that if you're actually running into this issue when trying to define a ForeignKey, you can simply pass the class name as a string and Django will resolve it.
|
8

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()

2 Comments

In the case of Django models, this is the only answer that works since we're trying to modify the Class itself, not instances of the Class. Django introspects the classes and uses that metadata to drive it's ORM layer.
I retract my comment - this is fine as a way to add class members to a Python class without needing forward decls. But it does not actually solve the problem for Django model declarations due to something (?) internal to the way Django processes these models.
4

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 Comment

the answer is no , but is weird , could you exemplify your solution ? when a depends on b , b depends on c and c depends on a .
1

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>

Comments

0

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.

4 Comments

I know I can do that to fix it (and is what I've done for now) but now my code ordering is non-intuitive.
Well, I see what you mean, but I think it would be more non-intuitive to try to use something that doesn't yet exist. Like doing: print a; a=5
Trying to run some statements such as print a; a=5 clearly doesn't make much sense, but a class is self-contained and perfectly reasonable to forward reference if C-style class prototypes were available.
Right, well, that sounds like implicit hell. In python things are executed in a straight line, and being "class definition code" makes no difference. What you were trying to do is exactly the same as: print a; a=5

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.