Class Variable Access and Assignment

G

Graham

This has to do with class variables and instances variables.

Given the following:

<code>

class _class:
var = 0
#rest of the class

instance_b = _class()

_class.var=5

print instance_b.var # -> 5
print _class.var # -> 5

</code>

Initially this seems to make sense, note the difference between to last
two lines, one is refering to the class variable 'var' via the class
while the other refers to it via an instance.

However if one attempts the following:

<code>

instance_b.var = 1000 # -> _class.var = 5
_class.var = 9999 # -> _class.var = 9999

</code>

An obvious error occurs. When attempting to assign the class variable
via the instance it instead creates a new entry in that instance's
__dict__ and gives it the value. While this is allowed because of
pythons ability to dynamically add attributes to a instance however it
seems incorrect to have different behavior for different operations.

There are two possible fixes, either by prohibiting instance variables
with the same name as class variables, which would allow any reference
to an instance of the class assign/read the value of the variable. Or
to only allow class variables to be accessed via the class name itself.

Many thanks to elpargo and coke. elpargo assisted in fleshing out the
best way to present this.

perhaps this was intended, i was just wondering if anyone else had
noticed it, and if so what form would you consider to be 'proper'
either referring to class variables via the class itself or via
instances of that class. Any response would be greatly appreciated.


Graham
 
S

Steven D'Aprano

On Thu, 03 Nov 2005 01:43:32 -0800, Graham wrote:

[snip]
print instance_b.var # -> 5
print _class.var # -> 5

</code>

Initially this seems to make sense, note the difference between to last
two lines, one is refering to the class variable 'var' via the class
while the other refers to it via an instance.

That's not correct. The line instance_b.var is referring to an instance
attribute. According to the usual Object Oriented model of inheritance, if
the instance does not have an attribute, the class is searched next.

So instance_b.var and _class.var are asking for two different things. The
first says, "Search the instance for attribute var, then the class." The
second says "Search the class."

BTW, a leading underscore is the convention for a private(ish) variable.
The convention for naming a variable after a reserved word is a trailing
underscore class_. In this case, there is also the convention that classes
should start with a capital, so you have Class and instance. (instance, of
course, is not a reserved word.)

However if one attempts the following:

<code>

instance_b.var = 1000 # -> _class.var = 5
_class.var = 9999 # -> _class.var = 9999

</code>

An obvious error occurs.

I see no error. No exception is raised when I try it: I get the expected
results. Assigning to an instance assigns to the instance, assigning to
the class assigns to the class. That's normal OO behaviour.

When attempting to assign the class variable
via the instance it instead creates a new entry in that instance's
__dict__ and gives it the value.

You might *want* to assign to the class attribute, but that's not what you
are doing. You are assigning to the instance.

Admittedly, it might not be the behaviour you expect, but it is the
standard behaviour in (as far as I know) all OO languages.

If you want to assign to the class attribute, you either assign to the
class directly, or use instance_b.__class__.var.

While this is allowed because of
pythons ability to dynamically add attributes to a instance however it
seems incorrect to have different behavior for different operations.

Surely you can't mean that? Why would you want different operations to
have the same behaviour?

There are two possible fixes, either by prohibiting instance variables
with the same name as class variables, which would allow any reference
to an instance of the class assign/read the value of the variable. Or
to only allow class variables to be accessed via the class name itself.

There is also a third fix: understand Python's OO model, especially
inheritance, so that normal behaviour no longer surprises you.
 
A

Antoon Pardon

Op 2005-11-03 said:
There is also a third fix: understand Python's OO model, especially
inheritance, so that normal behaviour no longer surprises you.

No matter wat the OO model is, I don't think the following code
exhibits sane behaviour:

class A:
a = 1

b = A()
b.a += 2
print b.a
print A.a

Which results in

3
1
 
S

Stefan Arentz

Antoon Pardon said:
No matter wat the OO model is, I don't think the following code
exhibits sane behaviour:

class A:
a = 1

b = A()
b.a += 2
print b.a
print A.a

Which results in

3
1

I find it confusing at first, but I do understand what happens :)

But really, what should be done different here?

S.
 
S

Steve Holden

Antoon said:
No matter wat the OO model is, I don't think the following code
exhibits sane behaviour:

class A:
a = 1

b = A()
b.a += 2
print b.a
print A.a

Which results in

3
1
I don't suppose you'd care to enlighten us on what you'd regard as the
superior outcome?

regards
Steve
 
A

Antoon Pardon

Op 2005-11-03 said:
I find it confusing at first, but I do understand what happens :)

I understand what happens too, that doesn't make it sane behaviour.
But really, what should be done different here?

I don't care what should be different. But a line with only one
referent to an object in it, shouldn't be referring to two different
objects.

In the line: b.a += 2, the b.a should be refering to the class variable
or the object variable but not both. So either it could raise an
attribute error or add two to the class variable.

Sure one could object to those sematics too, but IMO they are preferable
to what we have now.
 
P

Paul Rubin

Steve Holden said:
I don't suppose you'd care to enlighten us on what you'd regard as the
superior outcome?

class A:
a = []
b = A()
b.append(3)
print b.a
print a.a

Compare and contrast.
 
S

Steven D'Aprano

No matter wat the OO model is, I don't think the following code
exhibits sane behaviour:

class A:
a = 1

b = A()
b.a += 2
print b.a
print A.a

Which results in

3
1

Seems perfectly sane to me.

What would you expect to get if you wrote b.a = b.a + 2? Why do you expect
b.a += 2 to give a different result?

Since ints are immutable objects, you shouldn't expect the value of b.a
to be modified in place, and so there is an assignment to b.a, not A.a.

On the other hand, if this happened:

py> class A:
.... a = []
....
py> b = A()
py> b.a.append(None)
py> print b.a, A.a
[None], []

*then* you should be surprised.

(Note that this is not what happens: you get [None], [None] as expected.
The difference is that append modifies the mutable list in place.)
 
S

Stefan Arentz

....
I understand what happens too, that doesn't make it sane behaviour.


I don't care what should be different. But a line with only one
referent to an object in it, shouldn't be referring to two different
objects.

It doesn't.
In the line: b.a += 2, the b.a should be refering to the class variable
or the object variable but not both. So either it could raise an
attribute error or add two to the class variable.

It does exactly what you say. It adds 2 to the a *instance variable* of
the object instance in 'b'. It doesn't touch the *class variable* A.a
which is still 1.

S.
 
A

Antoon Pardon

Op 2005-11-03 said:
...


It doesn't.

Yes it does. If the b.a refers to the instance variable, then an
AttributeError should be raised, because the instance variable doesn't
exist yet, so you can't add two to it.

If the b.a refers to the class variable then two should be added to it.

Neither happens instead we get some hybrid in which an instance varible
is created that gets the value of class variable incrented by two.
It does exactly what you say. It adds 2 to the a *instance variable* of
the object instance in 'b'.

There is no instance variable at that point. How can it add 2, to
something that doesn't exist at the moment.
It doesn't touch the *class variable* A.a which is still 1.

But it accesses the class variable.
 
A

Antoon Pardon

Op 2005-11-03 said:
I don't suppose you'd care to enlighten us on what you'd regard as the
superior outcome?

No. I don't think a superior outcome is necessary to see that this is
not sane behaviour. I don't care that much on how it gets fixed.
 
S

Steve Holden

Paul said:
Steve Holden said:
I don't suppose you'd care to enlighten us on what you'd regard as the
superior outcome?


class A:
a = []
b = A()
b.append(3)
print b.a
print a.a

Compare and contrast.

append() guarantees to modify a mutable object in place. Augmented
assignment operations don't,but are "normally" equivalent to

name = name operator value

In the former case exactly such semantics are implemented. I still don;t
see anyone suggesting a better outcome for the augmented assignment.

regards
Steve
 
A

Antoon Pardon

Op 2005-11-03 said:
Seems perfectly sane to me.

What would you expect to get if you wrote b.a = b.a + 2?

I would expect a result consistent with the fact that both times
b.a would refer to the same object.
Why do you expect
b.a += 2 to give a different result?

I didn't know I did.
Since ints are immutable objects, you shouldn't expect the value of b.a
to be modified in place, and so there is an assignment to b.a, not A.a.

You are now talking implementation details. I don't care about whatever
explanation you give in terms of implementation details. I don't think
it is sane that in a language multiple occurence of something like b.a
in the same line can refer to different objects

I think it even less sane, if the same occurce of b.a refers to two
different objects, like in b.a += 2
 
V

venk

You see,
The seen behavior is due to the result of python's name
binding,scoping scheme.
Let me give you an example,
class A:
i=0
def t(self):
print self.i
self.i=4
then
a=A()
a.i is 0
a.t()
then,
A.i is 0
a.i is 4

In the function, it first searches for i in its local scope, on not
finding it, accesses the class object's i.
then the next line, is an assignment, which binds (creates a new
variable) in the instance's scope. you can use the buit-in id function
to verify it.

the same thing happens in the case of b.a = b.a + 2 .... search for b.a
not found, read the value from the enclosing scope (of the class
object).... then assign b.a to the local scope, with the value 3.

But, I think your question about the sanity of the behaviour should be
analysed sincerely....

if,
k=0
def f():
print k
k=k+1
raises UnboundLocalError, then how is it accepted in the former case?
hmmm....

maybe, my arguments are hapazard.... but, i'll get to know when i'm
flamed ;)
 
S

Sybren Stuvel

Antoon Pardon enlightened us with:
I would expect a result consistent with the fact that both times b.a
would refer to the same object.

"b.a" is just a name, not a pointer to a spot in memory. Getting the
value associated with that name is something different from assigning
a new value to that name.

Sybren
 
A

Antoon Pardon

Op 2005-11-03 said:
You see,
The seen behavior is due to the result of python's name
binding,scoping scheme.

I know what causes the behaviour. But I still think it is
not sane behaviour.

...

the same thing happens in the case of b.a = b.a + 2 .... search for b.a
not found, read the value from the enclosing scope (of the class
object).... then assign b.a to the local scope, with the value 3.

This is an explanation depending on a specific implementation.

Now can you give me a language design argument that supports the
idea that in "b.a = b.a + 2" b.a refers to two different objects.

And even if you could do that, can you give such an argument that
in "b.a += 2" that one occurence of b.a should refer to two different
objects.

Suppose I have code like this:

for i in xrange(1,11):
b.a = b.a + i

Now the b.a on the right hand side refers to A.a the first time through
the loop but not the next times. I don't think it is sane that which
object is refered to depends on how many times you already went through
the loop.
 
A

Antoon Pardon

Op 2005-11-03 said:
Antoon Pardon enlightened us with:

"b.a" is just a name, not a pointer to a spot in memory. Getting the
value associated with that name is something different from assigning
a new value to that name.

If that was all to it, one would expect the following to work to:
.... a += 2
....
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 2, in f
UnboundLocalError: local variable 'a' referenced before assignment

But I'll word it differently:

I would expect a result consistent with the fact that both times
b.a would be resolved in the same name space.
 
S

Stefan Arentz

Antoon Pardon said:
I know what causes the behaviour. But I still think it is
not sane behaviour.



This is an explanation depending on a specific implementation.

Now can you give me a language design argument that supports the
idea that in "b.a = b.a + 2" b.a refers to two different objects.

And even if you could do that, can you give such an argument that
in "b.a += 2" that one occurence of b.a should refer to two different
objects.

Your problem is a namespace conflict together with a certain
stubborness about lookup order :)

It is really simple. When you say b.a then the instance variable 'a'
is looked up first. If it does not exist then a class variable lookup
is done.

Remember, Python is a dynamic language.

It is all according to how things have been in Python for a long time.

The real issue here is that you should propery name class variables so
that there can't be any confusion about class or instance scope. I use
all uppercase identifiers for class variables for example.

S.
 
V

venk

hey,
did u read my reply fully? i too feel that this matter of raising
unbound local error in one case and not raising it in the other must be
analysed...

quoting from the documentation
"If a name binding operation occurs anywhere within a code block, all
uses of the name within the block are treated as references to the
current block. This can lead to errors when a name is used within a
block before it is bound. This rule is subtle. Python lacks
declarations and allows name binding operations to occur anywhere
within a code block. The local variables of a code block can be
determined by scanning the entire text of the block for name binding
operations."
 
A

Antoon Pardon

Op 2005-11-03 said:
Your problem is a namespace conflict together with a certain
stubborness about lookup order :)

It is really simple. When you say b.a then the instance variable 'a'
is looked up first. If it does not exist then a class variable lookup
is done.

Fine, we have the code:

b.a += 2

We found the class variable, because there is no instance variable,
then why is the class variable not incremented by two now?
Remember, Python is a dynamic language.

So? Python being a dynamic language doesn't prevent the following to fail:
.... a += 2
.... Traceback (most recent call last):
File "<stdin>", line 1, in ?
It is all according to how things have been in Python for a long time.

Unsane behaviour for a long time is still unsane behaviour.
The real issue here is that you should propery name class variables so
that there can't be any confusion about class or instance scope. I use
all uppercase identifiers for class variables for example.

The fact that this can be regarded as unwise coding, doesn't imply
it is sane behaviour of python. Variable shadowing happens. I don't
consider it sane behaviour if the same reference in a line gets
resolved in different name spaces
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top