4 ms·
While I agree that the "problem" case can be seen as obvious when considered in isolation, really it's the behaviour of the two cases taken together that can se
by radiowave 12y ago
While I agree that the "problem" case can be seen as obvious when considered in isolation, really it's the behaviour of the two cases taken together that can seem inconsistent. Nothing about understanding OOP or inheritance will prepare a person for that.
- vram22 12y ago>really it's the behaviour of the two cases taken together that can seem inconsistent. Why do you think so? I think that both cases seem consistent, or rather, correct (and therefore this example should not be treated as a common Python mistake), because x is not assigned a value anywhere in class C, and C inherits from A, so it should be clear to anyone knowing OOP and inheritance, that C's x is the same as A's x. (And the same holds true for inherited methods.) Even the OP says that in the post: >In other words, C doesn’t have its own x property, independent of A.
- radiowave 12y agoWhat's happening here is that a variable is inheriting its value from the superclass, except for when it doesn't. And when it doesn't, why is that? Well presumably it's because something's been overridden - OO tells us that's how we change the properties that are inherited from the superclass. No wait, that's not it; nothing's been overridden here. All that's happened is we've assigned a value to B.x, and doing so seems to have changed the inheritance of our class. So this variable is neither completely shared across classes and their subclasses (per Smalltalk class variables), nor completely independent across classes and their subclasses (per Smalltalk class instance variable), but instead its [in]dependence alters based upon whether (and where) you assign values to it. While I can understand that in terms of the dictionary mechanism used to implement it, from my point of view it's just weird behaviour.
- macbony 12y agoIf instead of x being an integer, it were a function x(), then it makes more sense. Really, for Python, there is no difference between the two in this example. When you assign a new value to B.x, you're overriding the value that B inherited from A. When you override the value in A, any subclass that doesn't have it's own overridden value will use the new A.x, but any subclass that is overridden will be unchanged.
- radiowave 12y agoI agree the behaviour makes sense for functions. I suppose I just prefer the idea that the meaning of assigning a value to a variable should be "assign this value to the variable", rather than "alter the inheritance behaviour of my class such that mutable state is stored in it where it wasn't stored before, and then assign this value to the variable."
- macbony 12y agox isn't a variable, it's a tag. Because Python is dynamically typed, x can be an int one minute and a function the next, so every attribute on a class is stored as a pointer to an object, including attributes that are integers (which is an object) and functions (which are also objects). Because you aren't declaring the type of x (Python doesn't allow that), Python has to treat x = 1 the same way it'd treat def x(self).
- radiowave 12y agoWhere x is an identifier (presumably aka tag?) that refers to a variable, it is not beyond the bounds of possibility for "x = 1" to be interpreted as "store the value of 1 into the variable that is referred to by identifier x". Plenty of languages, including dynamically typed ones, manage to do this, as indeed Python does in many cases. Not knowing the type of x is unrelated to question of where x's value is stored, or whether x's value will be stored somewhere else after we've assigned a new value to it.
- macbony 12y agoI'm not really sure I understand your point. The value of x is still going to be in memory, but you may not have any references to it and it will be garbage collected. The disconnect is in thinking x is a variable and not an identifier. x points to the object in the parent until it is overridden. This allows me to dynamically alter the functionality of a class and all it's subclasses that don't override that functionality during runtime.
- ndeine 12y agoThis is actually the same thing as regular Python scoping rules; there's not even any fancy OOP logic behind it. Here's the same thing, but using global scope and functions instead of classes and inheritance. >>> x = 1 >>> def a(): ...: print(x) ...: >>> def b(): ...: x = 2 ...: print(x) ...: >>> def c(): ...: print(x) ...: >>> a(), b(), c() 1 2 1 >>> x = 3 >>> a(), b(), c() 3 2 3 I think there is an argument to be made that classes are special and "reaching upwards" into the superclass scope should not occur - a unique copy should be made - but I also think that Python's way of doing it makes enough sense that it is not confusing. The Python devs are at least consistent about having their own way of doing things.
- radiowave 12y agoThat's an interesting point, that the behaviour of an inherited class variable is consistent with a case you show where inheritance plays no part at all. So from that point of view, it comes down to whether we expect that an inherited class variable really is just some variable in an outer scope that we can shadow with a local variable of the same name (per your example), or whether we expect that inheritance provides some stronger notion of ownership of the inherited variable. I dislike the former case, largely because I dislike the idea that the location at which a variable is stored can appear to change merely by assigning to it. But then, I dislike Python's implicit declaration of local variables for exactly the same reason. So you're right, there IS some consistency there. ;-)
- vram22 12y agoThis modified version of #2 might help clear things up. I've only added print statements. In general, when issues like this come up, printing the id()s of identifiers can help: Edit: Added some extra blank lines because lines were getting joined together. # class_variables.py class A(object): x = 1 class B(A): pass class C(A): pass print "Initially, A.x, B.x, C.x and their ids:" print A.x, B.x, C.x print id(A.x), id(B.x), id(C.x) B.x = 2 print "After B.x = 2, A.x, B.x, C.x and their ids:" print A.x, B.x, C.x print id(A.x), id(B.x), id(C.x) A.x = 3 print "After A.x = 3, A.x, B.x, C.x and their ids:" print A.x, B.x, C.x print id(A.x), id(B.x), id(C.x) And the output: >python class_variables.py Initially: A.x, B.x, C.x and their ids: 1 1 1 30519808 30519808 30519808 After B.x = 2: A.x, B.x, C.x and their ids: 1 2 1 30519808 30519796 30519808 After A.x = 3: A.x, B.x, C.x and their ids: 3 2 3 30519784 30519796 30519784