0

This is an easy way to earn some points. Please explain the following:

class C:
    a = {}
    b = 0
    c = []

    def __init__(self):
        self.x = {}

    def d(self, k, v):
        self.x[k] = v
        self.a[k] = v;
        self.b = v
        self.c.append(v)

    def out(self, k):
        print(self.x[k], self.a[k], self.b, self.c[0])

c = C()
d = C()
c.d(1, 10)
d.d(1, 20)
c.out(1)  
d.out(1)

Will output the following:

10 20 10 10
20 20 20 10

Why does a dictionary, a list and 'plain' variable each behave differently?

Edit: I was thinking the question is obvious but let me spell it in greater detail:

I have a class with three attributes, a, b, and c. I create two instances of the class. I then call a method that modifies these attributes for each instance. When I inspect the attributes, I find that if an attribute is a dictionary, it is shared across all instances, while if it is a 'plain' variable, it behaves as one would expect, being different for each instance.

martin
  • 2,520
  • 22
  • 29
  • "Why does a dictionary, an array and 'plain' variable each behave differently?" They behave differently because they're different classes. That question doesn't make any sense at all. What **specific** difference do you want to know about? – S.Lott Sep 01 '11 at 22:24

2 Answers2

5

First of all, [] is not an array, it's a list. The issue here is how the attribute resolution and mutable variables work. Let's start with

class Foo(object):
    a = {}
    b = 0
    c = []

This creates a class with three attributes — those are available either through class itself (Foo.a, for example), or through class' instance (Foo().a). Attributes are stored in a special thingy called __dict__. Both class and an instance have one (there are cases in which this is not true, but they're irrelevant here) — but in the case of Foo, the instance __dict__ is empty when the instance is created — so when you do Foo().a, in reality you're accessing the same object as in Foo.a.

Now, you're adding __init__.

class Foo(object):
    # ...

    def __init__(self):
        self.x = {}

This creates an attribute not in the class' __dict__, but in the instance one, so you cannot access Foo.x, only Foo().x. This also means x is a whole different object in every instance, whereas class attributes are shared by all of the class instances.

Now you're adding your mutation method.

class Foo(object):
    # ...

    def mutate(self, key, value):
        self.x[key] = value
        self.a[key] = value
        self.b      = value
        self.c.append(value)

Do you recall that self.x = {} creates an instance attribute? Here self.b = value does the same exact thing — it doesn't touch the class attribute at all, it creates a new one that for instances overshadows the shared one (that's how references work in Python — assignment binds the name to an object, and never modifies the object that the name was pointing to).

But you don't rebind self.a and self.c — you mutate them in-place (because they're mutable and you can do that) — so in fact you're modifying the original class attributes, that's why you can observe the change in the other instance (as those are shared by them). self.x behave differently, because it's not a class attribute, but rather an instance one.

You also print only first element of self.c — if you'd print all of it, you'd see it's [10, 20].

Cat Plus Plus
  • 125,936
  • 27
  • 200
  • 224
  • If I understand you correctly, what it boils down to is: `self.b = value` creates new entry in `instance.__dict__` while `self.a[key] = value` doesn't. I think that is the answer to my question: the instance dictionary is not populated when the instance is created, but only when the attribute is assigned to. Is this correct? – martin Sep 02 '11 at 07:56
  • @martin: Yup. The default getattr first checks the instance dict, and then class dict if attribute wasn't found (+ bases and stuff, but basically this). – Cat Plus Plus Sep 02 '11 at 09:59
2

a and c are class attributes b and x are instance attributes.

You should read and understand Python: Difference between class and instance attributes

Community
  • 1
  • 1
cnelson
  • 1,355
  • 11
  • 14