Thursday, July 17, 2008

Python class inconsistencies

Update:

Masklinn's correction is obviously correct -- this isn't primarily a class v. instance variable problem, it's the difference between the operators I'm using on the instance variables.

So, an assignment (k.t = ...) points the attribute at a wholly new object, while accessing one of the attributes' methods actually alters the attributes' state.

(Which is where the confusion over mutability comes into play, but it was still confusion on my part.)

My original posting:

This makes a modicum of sense if you have a basic grasp of the distinction between mutables and immutables in Python, but it still seems like a mess.


>>> class K(object):
>>> t = (1,2,3)
>>> l = [1,2,3]
>>>
>>> k = K()
>>> j = K()
>>>
>>> k.t = ('a','b','c')
>>> j.l.append( 10 )
>>>
>>> j.t
(1, 2, 3)
>>> k.l
[1, 2, 3, 10]

Do you see it?

Class K defines two attributes, a tuple t and a list l.

If you instantiate K twice (k and j), then change the t attribute of one and the l attribute of the other, the change to l will be shared between instances, while the change to t will only effect the instance.

Whether an attribute belongs to the class or the instance depends on whether it's type is mutable or not!

There's a fix in one case: you can use __init__ to make mutable data instance-specific:

>>> class K(object):
>>> t = (1,2,3)
>>> l = [1,2,3]
>>> def __init__(self):
>>> self.l = [1,2,3]
Now changes to k.l won't effect j.l

However, I can't find a way to accomplish the opposite--create a class attribute for an immutable data type--w/out resorting to __get_attribute__ magic.

1 comment:

Masklinn said...

> This makes a modicum of sense if you have a basic grasp of the distinction between mutables and immutables in Python, but it still seems like a mess.

Wrong, this is completely unrelated to mutables and immutables (but completely unrelated to you not understanding what's happening)

> If you instantiate K twice (k and j), then change the t attribute of one and the l attribute of the other, the change to l will be shared between instances, while the change to t will only effect the instance.

That's absolutely not what happens

> Whether an attribute belongs to the class or the instance depends on whether it's type is mutable or not!

No it doesn't. Both attributes belong to the class

When you do `j.l.append(whatever)` you get a reference to "l" then modify it in place (by appending). Good.

Now when you do `k.t = whatever`, there is no modification. Python simply affects `whatever` to k.t... creating the `t` instance variable instead of replacing the reference to the cls variable. You would get strictly the same result if you did `j.l = whatever`, it's completely unrelated to mutability and completely related to Python's semantic in accessing and altering class attributes references through instances (it doesn't).

Here's your lesson for today: you don't ever access class attributes through instances. Either you do it directly through the class (try `K.t = ('a', 'b', 'c')`, you'll see that the modifications are reflected to both j and k), or you use `cls(instance_variable)`