?

Log in

No account? Create an account
   Journal    Friends    Archive    Profile    Memories
 

Python - morfizm


Mar. 16th, 2011 06:14 pm Python

Python has a different concept of static variables (to be precise, static class members). I had to spend a few minutes to wrap my head around it. In the following example, you'll see that static variables are being copied as references to all child objects. When they change reference to a new object, it's no longer static: not visible from instance of class type, or from other class instances. When you change only value (modify the object referenced), this is seen from everywhere, as I would expect for static objects.

So the right way to think of static variables is that you're providing a default, which will be copied into class type instance once. Hence, if it's a mutable object, it will be shared, except for class instances that replace it, and if it's immutable, it won't be shared.

Enjoy :)

class MutableValue():
    def __init__(self, value):
        self.value = value

    def __repr__(self):
        return str(self.value)

class MyClass():
    var = MutableValue(1)

def main():
    c1 = MyClass()
    c2 = MyClass()
    c3 = MyClass()
    print(MyClass.var, c1.var, c2.var, c3.var) 
    # 1, 1, 1, 1 -- all values equal
    
    c1.var = 2
    print(MyClass.var, c1.var, c2.var, c3.var) 
    # 1, 2, 1, 1 -- c1 changed to 2, other fields unaffected

    MyClass.var.value = 3
    print(MyClass.var, c1.var, c2.var, c3.var) 
    # 3, 2, 3, 3 -- ...woops! Everything is now 3 =)
    # except for c1 where we replaced the value

    c2.var.value = 4
    print(MyClass.var, c1.var, c2.var, c3.var)
    # 4, 2, 4, 4 -- and now everything is 4, except for c1

main()
(Syntax highligher used: http://tohtml.com/python/)

Current Mood: amusedamused

13 comments - Leave a commentPrevious Entry Share Next Entry

Comments:

From:dennyrolling
Date:March 17th, 2011 01:37 am (UTC)
(Link)
I know nothing* about Python, but isn't this an equivalent to
class MV {
static int i;
};

class MyClass {
Object* pi = &(MV.i);
} ;

you can't access c1.var.value, can you?
From:archaicos
Date:March 17th, 2011 05:26 am (UTC)
(Link)
In C++ you can hack your way to access everything. And shoot yourself dead too. :)
From:dennyrolling
Date:March 17th, 2011 03:58 pm (UTC)
(Link)
I love #define private public trick
From:morfizm
Date:March 17th, 2011 04:24 pm (UTC)
(Link)
:)))
From:morfizm
Date:March 17th, 2011 04:24 pm (UTC)
(Link)
Btw... can you #define 0 1 ?
From:dennyrolling
Date:March 17th, 2011 06:47 pm (UTC)
(Link)
I think that you can not start identifier with a digit

oh codepad is back up: http://codepad.org/Ki9xTLxd

but another (useless) classic is:
#define true false // happy debugging, motherf*^&ers!
From:morfizm
Date:March 17th, 2011 06:30 am (UTC)
(Link)
I am not sure if I can follow your thought.
Of course you can access c1.var.value (see accessing c2.var.value in my example, even write access)

In C++ you can define static member variables - these are values shared among all class instances. You define them as:

struct MyClass {
static int var;
};

and then assign value:
MyClass::var = 1;

You get one instance of "var" for any MyClass instances.
You can also refer to it without any class instance at all, e.g. MyClass::var. You don't need MyClass object.

The problem is that static member variables in Python behave, on one hand, like C++ statics: you can refer to them without having an object. On another hand, like normal members: you can change them in object (with assignment: c1.var = 2), and they "unbind" from the class-wide copy. However, if you change only fields, but not reference, like in the next example in post, then you manipulate the global object.

This was not intuitive for me, so I wrote this post.
From:dennyrolling
Date:March 17th, 2011 03:53 pm (UTC)
(Link)
what I meant was "after assigning 2 to c1.var you can't access c1.var.value (as it is not a MutableValue anymore)". another interesting test would be:
MyClass.var = 2; // and see if c2 is still connected to c3
I'd do it, but codepad is down at the moment.
From:morfizm
Date:March 17th, 2011 06:11 pm (UTC)
(Link)

c2.var.value = 4
print(MyClass.var, c1.var, c2.var, c3.var)
# 4, 2, 4, 4 -- and now everything is 4, except for c1

MyClass.var = 5
print(MyClass.var, c1.var, c2.var, c3.var)
# 5, 2, 5, 5 -- wow, this is very unexpected indeed.

So, I think what's happening is that there are two dictionaries of attribute-value pairs. One for class type, one for each instance. Read lookups first look into object's dictionary, and then looks into class, if object didn't have 'var' defined. Write lookups depend on whether you access variable via object name or via class name.
From:dennyrolling
Date:March 17th, 2011 06:53 pm (UTC)
(Link)
yeah I guess I need to read something on Python, last test was unexpected for me as well.

I signed up for the class in the end of April
From:morfizm
Date:March 17th, 2011 06:33 am (UTC)
(Link)
So, it's more like:

struct MV {
int i;
}

struct MyClass {
static MV* var;
};

But only before you attempt to assign into var from an instance of MyClass. Then it magically turns into:

struct MyClass {
MV* (or anything*) var;
};

which isn't static anymore. However, static copy is still accessible via class name (MyClass.var)
From:_m_e_
Date:March 17th, 2011 06:52 am (UTC)
(Link)
I assume this is also true with everything defined class-level: fields, methods, etc. I.e. you can change class's method after creating instance, and the change will only apply to new instances, while old instances inherit original method?
From:morfizm
Date:March 26th, 2011 04:05 am (UTC)
(Link)
Your assumption is easy to verify, and it is correct:

class MyClass():
    y1 = 1
    y2 = 2
    y3 = 3

    def x1(self):
        return self.y1

    def x2(self):
        return self.y2

    def x3(self):
        return self.y3


def main():
    c1 = MyClass()
    c2 = MyClass()
    c3 = MyClass()
    print(c1.x1(), c2.x1(), c3.x1()) # 1 1 1

    c2.x1 = c2.x2

    print(c1.x1(), c2.x1(), c3.x1()) # 1 2 1 

    MyClass.x1 = MyClass.x3
    
    print(c1.x1(), c2.x1(), c3.x1()) # 3 2 3 

main()