tags:

views:

1677

answers:

5

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

+18  A: 

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()
Joe Holloway
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?
Dana
Yes, since the c=C() is in the class definition (executed upon module load), class C does not yet exist.
truppo
@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
Joe Holloway
class level variables are defined at class definition time -- you rarely have any use for class variables. Instance variables are defined when an instance is created. You almost always want these.
S.Lott
This just goes to show how little I use class variables. I'd never run into this or even considered it was a what-if.
Dana
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.
Mat
@Mat Django uses class attributes to provide a *declarative* object-relational mapping API. They use meta-classes to map these declarations into instance-level attributes. I don't recommend starting with the models API to understand Python's OOP constructs. I would start with something more basic.
Joe Holloway
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.
Carl Meyer
Very true Carl. I've resolved it by ordering my classes to ensure they're declared prior to being referenced. But that is a very good point if reordering is insufficient (circular references, etc.)
Mat
self.c will add an instance property, not a class level property.
verveguy
@verveguy And how does that deserve a -1 exactly? This post is over a year old, had 20 upvotes and now you think it's wrong? I think perhaps you didn't read his question, nor my response, nor the comments under the response. It's clear that my answer addressed his underlying misunderstanding of the OOP model in Python.
Joe Holloway
Joe: your answer was informative about Python's OOP model, true enough, but was not what the OP was looking for. He wanted to declare a *class level* property, and instead you helped him declare an instance property. Your statement "This is what you are looking for" is simply not true, hence my comment. Sorry that this bothers you.
verveguy
+7  A: 

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()
truppo
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.
verveguy
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.
verveguy
A: 

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.

Ali A
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.
Mat
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
Ali A
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.
Mat
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
Ali A
+1  A: 

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.

Erik Engbrecht
+1  A: 

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

verveguy
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.
Joe Holloway