Class properties and object properties

Discussion in 'Python' started by SuperZE, Oct 6, 2008.

  1. SuperZE

    SuperZE Guest

    Learning python I was rewriting some of my old programs to see the
    pros and cons of python when a steped in some weird (at least for me)
    behavior.

    Here it is simplified

    The code:

    >>> class Test1:

    myList = [4 for n in range(4)]
    myInt = 4
    >>> a = Test1()
    >>> b = Test1()
    >>> a.myList

    [4, 4, 4, 4]
    >>> a.myInt

    4
    >>> b.myList

    [4, 4, 4, 4]
    >>> b.myInt

    4
    >>> b.myList[2] = 3
    >>> b.myInt = 3
    >>> b.myList

    [4, 4, 3, 4]
    >>> b.myInt

    3
    >>> a.myList

    [4, 4, 3, 4]
    >>> a.myInt

    4


    I would not expect the second a.myList to have changed as it did
    since, for me, I have only changed b.myList. And also, why only the
    list changed and not the integer?

    One thing i tried was:

    >>> class Test2:

    myList = []
    myInt = 4
    def __init__(self):
    self.myList = [4 for n in range(4)]

    >>> a = Test2()
    >>> b = Test2()
    >>> a.myList

    [4, 4, 4, 4]
    >>> b.myList

    [4, 4, 4, 4]
    >>> b.myList[2] = 3
    >>> b.myList

    [4, 4, 3, 4]
    >>> a.myList

    [4, 4, 4, 4]


    And as you see it worked as I expected.

    Now the question, why?
    SuperZE, Oct 6, 2008
    #1
    1. Advertising

  2. SuperZE wrote:

    > Learning python I was rewriting some of my old programs to see the
    > pros and cons of python when a steped in some weird (at least for me)
    > behavior.
    >
    > Here it is simplified
    >
    > The code:
    >
    >>>> class Test1:

    > myList = [4 for n in range(4)]
    > myInt = 4
    >>>> a = Test1()
    >>>> b = Test1()
    >>>> a.myList

    > [4, 4, 4, 4]
    >>>> a.myInt

    > 4
    >>>> b.myList

    > [4, 4, 4, 4]
    >>>> b.myInt

    > 4
    >>>> b.myList[2] = 3
    >>>> b.myInt = 3
    >>>> b.myList

    > [4, 4, 3, 4]
    >>>> b.myInt

    > 3
    >>>> a.myList

    > [4, 4, 3, 4]
    >>>> a.myInt

    > 4
    >
    >
    > I would not expect the second a.myList to have changed as it did
    > since, for me, I have only changed b.myList. And also, why only the
    > list changed and not the integer?
    >
    > One thing i tried was:
    >
    >>>> class Test2:

    > myList = []
    > myInt = 4
    > def __init__(self):
    > self.myList = [4 for n in range(4)]
    >
    >>>> a = Test2()
    >>>> b = Test2()
    >>>> a.myList

    > [4, 4, 4, 4]
    >>>> b.myList

    > [4, 4, 4, 4]
    >>>> b.myList[2] = 3
    >>>> b.myList

    > [4, 4, 3, 4]
    >>>> a.myList

    > [4, 4, 4, 4]
    >
    >
    > And as you see it worked as I expected.
    >
    > Now the question, why?


    Because you declare myList to be a *class*-level variable, which means *all*
    instances of that class (a and b in your case) *share* it. Python does not
    declare *instance* variables the way you do.

    Instead, do this:

    class Foo(object):
    def __init__(self):
    self.myList = []

    And to avoid a common pitfall you are likely to hit next: default arguments
    in functions (and methods) are evaluated only *once*, when defined - not
    later!

    So

    def foo(some_arg=[]):
    some_arg.append("a")
    print some_arg

    foo()
    foo()

    will result in

    ['a']
    ['a', 'a']

    being printed out.

    Diez
    Diez B. Roggisch, Oct 6, 2008
    #2
    1. Advertising

  3. SuperZE

    SuperZE Guest

    > Because you declare myList to be a *class*-level variable, which means *all*
    > instances of that class (a and b in your case) *share* it. Python does not
    > declare *instance* variables the way you do.
    >
    > Instead, do this:
    >
    > class Foo(object):
    >     def __init__(self):
    >         self.myList = []



    Interesting, but that does not explain the difference in the behavior
    of myList and myInt

    Both were class-level variables, as far as I can see, and therefor a
    and b should also share it


    And good remind on default arguments :)
    SuperZE, Oct 6, 2008
    #3
  4. SuperZE wrote:

    >> Because you declare myList to be a *class*-level variable, which means
    >> *all* instances of that class (a and b in your case) *share* it. Python
    >> does not declare *instance* variables the way you do.
    >>
    >> Instead, do this:
    >>
    >> class Foo(object):
    >> def __init__(self):
    >> self.myList = []

    >
    >
    > Interesting, but that does not explain the difference in the behavior
    > of myList and myInt


    Sorry, I forgot that.

    The reason is simple - lists are mutable. Numbers aren't. And when you
    *assign* to an instance a value, it will be set on the instance, not on the
    class.

    The same happens (as you've already seen for yourself) when you do

    a.myList = []

    as this creates a new list, stored only on a.


    > Both were class-level variables, as far as I can see, and therefor a
    > and b should also share it


    Nope. To change it, you need to do

    Test1.myInt = 5

    The thing is that the lookup triggered by getattr/dotted notation goes
    roughly like this:

    1) try to find the attribute (value *or* method! Python doesn't distinguish)
    in the instance itself.

    2) if not found there, look at the class.

    3) if not found there, look at the base-classes in the method resolution
    order (MRO)

    There are some other things involved - descriptor protocol calls on the
    class-level lookups - but that's not relevant for the problem at hand.

    OTOH, when setting with setattr/dotted notation, it will always set on the
    object passed.

    Diez
    Diez B. Roggisch, Oct 6, 2008
    #4
  5. SuperZE

    Jerry Hill Guest

    On Mon, Oct 6, 2008 at 9:38 AM, SuperZE <> wrote:
    > Interesting, but that does not explain the difference in the behavior
    > of myList and myInt
    >
    > Both were class-level variables, as far as I can see, and therefor a
    > and b should also share it


    They did share it, until you assigned an instance variable in b, which
    shadowed the class variable. Example:

    >>> class Test1:

    myInt = 4


    >>> a = Test1()
    >>> b = Test1()
    >>> a.myInt

    4
    >>> b.myInt

    4
    >>> Test1.myInt

    4
    >>> b.myInt = 3
    >>> a.myInt

    4
    >>> b.myInt

    3
    >>> Test1.myInt

    4

    As soon as you bound the name b.myInt to a new value, it created an
    instance variable. That hides the value of Test1.myInt.

    --
    Jerry
    Jerry Hill, Oct 6, 2008
    #5
  6. SuperZE

    SuperZE Guest

    TYVM Diez and Jerry

    Now I understand how this works
    SuperZE, Oct 6, 2008
    #6
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. E11
    Replies:
    1
    Views:
    4,708
    Thomas Weidenfeller
    Oct 12, 2005
  2. Replies:
    9
    Views:
    557
    justanotherguy
    Dec 3, 2004
  3. Sergio Correia
    Replies:
    7
    Views:
    281
    Ben Finney
    Sep 18, 2007
  4. DanielJohnson
    Replies:
    7
    Views:
    1,993
    James Kanze
    Jan 17, 2009
  5. Replies:
    1
    Views:
    113
Loading...

Share This Page