8

I'm new to python and learned that class attributes are like static data members in C++. However, I got confused after trying the following code:

>>> class Foo:
...     a=1
... 
>>> f1=Foo();
>>> f2=Foo()
>>> f1.a
1
>>> f1.a=5
>>> f1.a
5
>>> f2.a
1

Shouldn't f2.a also equal 5?

If a is defined as a list instead of an integer, the behavior is expected:

>>> class Foo:
...     a=[]
... 
>>> f1=Foo();
>>> f2=Foo()
>>> f1.a
[]
>>> f1.a.append(5)
>>> f1.a
[5]
>>> f2.a
[5]

I looked at Python: Difference between class and instance attributes, but it doesn't answer my question.

Can anyone explain why the difference? Thanks

Community
  • 1
  • 1
user11869
  • 1,083
  • 2
  • 14
  • 29
  • possible duplicate of [How do I avoid having Python class data shared among instances?](http://stackoverflow.com/questions/1680528/how-do-i-avoid-having-python-class-data-shared-among-instances) – Martijn Pieters Feb 23 '13 at 15:41

6 Answers6

10

Python's class attributes and object attributes are stored in separate dictionaries. For the object f1, these can be accessed via, respectively, f1.__class__.__dict__ and f1.__dict__. Executing print f1.__class__ is Foo will output True.

When you reference the attribute of an object, Python first tries to look it up in the object dictionary. If it does not find it there, it checks the class dictionary (and so on up the inheritance heirarchy).

When you assign to f1.a, you are adding an entry to the object dictionary for f1. Subsequent lookups of f1.a will find that entry. Lookups of f2.a will still find the class attribute — the entry in the class attribute dictionary.

You can cause the value of f1.a to revert to 1 by deleting it:

del f1.a

This will remove the entry for a in the object dictionary of f1, and subsequent lookups will continue on to the class dictionary. So, afterwards, print f1.a will output 1.

intuited
  • 23,174
  • 7
  • 66
  • 88
8

You're not doing the same thing in your second example. In you first example, you are assigning f1.a a new value:

f1.a = 5

In your second example, you are simply extending a list:

f1.a.append(5)

This doesn't change what f1.a is pointing to. If you were instead to do this:

f1.a = [5]

You would find that this behaves the same as your first example.

But consider this example:

>>> f1=Foo()
>>> f2=Foo()
>>> Foo.a = 5
>>> f1.a
5
>>> f2.a
5

In this example, we're actually changing the value of the class attribute, and the change is visible in all instances of the class. When you type:

f1.a = 5

You're overriding the class attribute with an instance attribute.

larsks
  • 277,717
  • 41
  • 399
  • 399
  • 2
    "You're overriding the class attribute with an instance attribute." I don't think so. The class attribute remains untouched. In the code of user 11869, the attribute **a** is CREATED in the namspace of **f1** by the instruction ``f1.a = 5`` , because this attribute wasn't existing before, as it is shown in my answer using **\_\_dict__** – eyquem Sep 26 '11 at 10:36
7
>>> class Foo:
...     a=1
... 
>>> f1=Foo()
>>> f2=Foo()
>>> f1.a    # no instance attribute here in f1, so look up class attribute in Foo
1
>>> f1.a=5  # create new instance attribute in f1
>>> f1.a    # instance attribute here. Great, let's use this.
5
>>> f2.a    # no instance attribute in f2, look up class attribute in Foo
1
>>>
>>> class Foo:
...     a=[]
... 
>>> f1=Foo()
>>> f2=Foo()
>>> f1.a            # no instance attribute - look up class attribute in Foo
[]
>>> f1.a.append(5)  # no instance attribute, modify class attribute in-place
>>> f1.a            # no instance attribute - look up class attribute in Foo
[5]
>>> f2.a            # same here
[5]
Tim Pietzcker
  • 328,213
  • 58
  • 503
  • 561
4

My advice: to understand such cases, do tests using id, and __dict__ too :

class Foo:
    a=1

# Foo.a == 1
# id(Foo.a) == 10021840
# Foo.__dict__ == {'a': 1, '__module__': '__main__', '__doc__': None}


f1 = Foo()

# f1.a == 1
# id(f1.a) == 10021840
# f1.__dict__ == {}

f2 = Foo()

# f2.a == 1
# id(f2.a) == 10021840
# f2.__dict__ == {}

f1.a = 5

# f1.a == 5
# id(f1.a) == 10021792
# f1.__dict__ == {'a': 5}

# f2.a == 1
# id(f2.a) == 10021840
# f2.__dict__ == {}

This shows that as long as the instruction f1.a = 5 has not been executed, the instance f1 doesn't have a personal attribute a.

Then, why does the instruction print f1.a executed before f1.a = 5 produce 1?
That's because:

A class instance has a namespace implemented as a dictionary which is the first place in which attribute references are searched. When an attribute is not found there, and the instance’s class has an attribute by that name, the search continues with the class attributes.

http://docs.python.org/reference/datamodel.html#the-standard-type-hierarchy

agf
  • 171,228
  • 44
  • 289
  • 238
eyquem
  • 26,771
  • 7
  • 38
  • 46
0

What happens is the following:

When you instantiate a new object f1 = Foo(), it does not have any attributes of its own. Whenever you try to access for example f1.a, you get redirected to the class’s Foo.a:

print f1.__dict__
{}

print f1.a
1

However, if you set f1.a = 5, the instance gets a new attribute of that value:

print f1.__dict__
{'a': 5}

The class definition is untouched by this, as are any other instances.

In the second example, you do not reassign anything. With append you are only using that very same list which was defined in the class. Therefore, your instance still refers to that list, as do all other instances.

Debilski
  • 66,976
  • 12
  • 110
  • 133
  • 1
    Same as the answer I was about to type. I'd just add that the reason it "works" with a list is because in that case the object is [mutable](http://docs.python.org/library/stdtypes.html#typesseq-mutable). Since all instances refer to the same object, modifications to that object are seen by them all. – Greg Haskins Sep 25 '11 at 21:12
  • @Debilski: new instances actually often have attributes of their own (the `f1.__dict__` that you write in your answer is one of them, as can be see be comparing `id(Foo.a)` and `id(f1.a)`). – Eric O. Lebigot Sep 25 '11 at 21:16
0

When you assign an attribute in python, it doesn't matter where that attribute might already be defined, the new assignment is always applied to the object assigned to. When you say

>>> f1.a=5

The object that has the attribute here is the instance, so it's the instance that gets the new value.

SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304