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.
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.
>>> 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.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. ;-)
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."
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.
(edit: replaced "an expression" with "a statement")
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