What is the difference between class and instance attributes?
Asked Answered
V

5

160

Is there any meaningful distinction between:

class A(object):
    foo = 5   # some default value

vs.

class B(object):
    def __init__(self, foo=5):
        self.foo = foo

If you're creating a lot of instances, is there any difference in performance or space requirements for the two styles? When you read the code, do you consider the meaning of the two styles to be significantly different?

Vireo answered 16/10, 2008 at 0:23 Comment(2)
I just realized that a similar question was asked and answered here: #207234 Should I delete this question?Vireo
It's your question, feel free to delete it. Since it's yours, why ask anyone else's opinion?Doubtful
U
171

There is a significant semantic difference (beyond performance considerations):

  • when the attribute is defined on the instance (which is what we usually do), there can be multiple objects referred to. Each gets a totally separate version of that attribute.
  • when the attribute is defined on the class, there is only one underlying object referred to, so if operations on different instances of that class both attempt to set/(append/extend/insert/etc.) the attribute, then:
    • if the attribute is a builtin type (like int, float, boolean, string), operations on one object will overwrite (clobber) the value
    • if the attribute is a mutable type (like a list or a dict), we will get unwanted leakage.

For example:

>>> class A: foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo
[5]
>>> class A:
...  def __init__(self): self.foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo    
[]
Unmoral answered 16/10, 2008 at 1:26 Comment(4)
Only the mutable types are shared. Like for int and str they still attached with each instances rather than class.Weathering
@Babu: No, int and str are also shared, in exactly the same way. You can check that easily with is or id. Or just look in each instance's __dict__ and the class's __dict__. It just usually doesn't matter very much whether immutable types are shared or not.Sept
However, note that if you do a.foo = 5, then in both cases you will see b.foo return []. That is because in the first case, you are overwriting the class attribute a.foo with a new instance attribute of the same name.Outpatient
The "leakage" may actually be wanted by design.Extremist
I
45

The difference is that the attribute on the class is shared by all instances. The attribute on an instance is unique to that instance.

If coming from C++, attributes on the class are more like static member variables.

Impetrate answered 16/10, 2008 at 8:16 Comment(2)
Isn't it only mutable types that are shared? The accepted answer shows a list, which works, but if it is an int, it seems to be the same as an instance attr: >>> class A(object): foo = 5 >>> a, b = A(), A() >>> a.foo = 10 >>> b.foo 5Vareck
@Rafe: No, all types are shared. The reason you're confused is that what a.foo.append(5) is mutating the value that a.foo refers to, while a.foo = 5 is making a.foo into a new name for the value 5. So, you end up with an instance attribute that hides the class attribute. Try the same a.foo = 5 in Alex's version and you'll see that b.foo is unchanged.Sept
A
44

Here is a very good post, and summary it as below.

class Bar(object):
    ## No need for dot syntax
    class_var = 1

    def __init__(self, i_var):
        self.i_var = i_var

## Need dot syntax as we've left scope of class namespace
Bar.class_var
## 1
foo = Bar(2)

## Finds i_var in foo's instance namespace
foo.i_var
## 2

## Doesn't find class_var in instance namespace…
## So look's in class namespace (Bar.__dict__)
foo.class_var
## 1

And in visual form

enter image description here

Class attribute assignment

  • If a class attribute is set by accessing the class, it will override the value for all instances

      foo = Bar(2)
      foo.class_var
      ## 1
      Bar.class_var = 2
      foo.class_var
      ## 2
    
  • If a class variable is set by accessing an instance, it will override the value only for that instance. This essentially overrides the class variable and turns it into an instance variable available, intuitively, only for that instance.

      foo = Bar(2)
      foo.class_var
      ## 1
      foo.class_var = 2
      foo.class_var
      ## 2
      Bar.class_var
      ## 1
    

When would you use class attribute?

  • Storing constants. As class attributes can be accessed as attributes of the class itself, it’s often nice to use them for storing Class-wide, Class-specific constants

      class Circle(object):
           pi = 3.14159
    
           def __init__(self, radius):
                self.radius = radius   
          def area(self):
               return Circle.pi * self.radius * self.radius
    
      Circle.pi
      ## 3.14159
      c = Circle(10)
      c.pi
      ## 3.14159
      c.area()
      ## 314.159
    
  • Defining default values. As a trivial example, we might create a bounded list (i.e., a list that can only hold a certain number of elements or fewer) and choose to have a default cap of 10 items

      class MyClass(object):
          limit = 10
    
          def __init__(self):
              self.data = []
          def item(self, i):
              return self.data[i]
    
          def add(self, e):
              if len(self.data) >= self.limit:
                  raise Exception("Too many elements")
              self.data.append(e)
    
       MyClass.limit
       ## 10
    
Antivenin answered 7/12, 2015 at 3:59 Comment(1)
The post you linked is amazing!Jeffrey
S
23

Since people in the comments here and in two other questions marked as dups all appear to be confused about this in the same way, I think it's worth adding an additional answer on top of Alex Coventry's.

The fact that Alex is assigning a value of a mutable type, like a list, has nothing to do with whether things are shared or not. We can see this with the id function or the is operator:

>>> class A: foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
True
>>> class A:
...     def __init__(self): self.foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
False

(If you're wondering why I used object() instead of, say, 5, that's to avoid running into two whole other issues which I don't want to get into here; for two different reasons, entirely separately-created 5s can end up being the same instance of the number 5. But entirely separately-created object()s cannot.)


So, why is it that a.foo.append(5) in Alex's example affects b.foo, but a.foo = 5 in my example doesn't? Well, try a.foo = 5 in Alex's example, and notice that it doesn't affect b.foo there either.

a.foo = 5 is just making a.foo into a name for 5. That doesn't affect b.foo, or any other name for the old value that a.foo used to refer to.* It's a little tricky that we're creating an instance attribute that hides a class attribute,** but once you get that, nothing complicated is happening here.


Hopefully it's now obvious why Alex used a list: the fact that you can mutate a list means it's easier to show that two variables name the same list, and also means it's more important in real-life code to know whether you have two lists or two names for the same list.


* The confusion for people coming from a language like C++ is that in Python, values aren't stored in variables. Values live off in value-land, on their own, variables are just names for values, and assignment just creates a new name for a value. If it helps, think of each Python variable as a shared_ptr<T> instead of a T.

** Some people take advantage of this by using a class attribute as a "default value" for an instance attribute that instances may or may not set. This can be useful in some cases, but it can also be confusing, so be careful with it.

Sept answered 29/10, 2014 at 23:32 Comment(0)
T
0

There is one more situation.

Class and instance attributes is Descriptor.

# -*- encoding: utf-8 -*-


class RevealAccess(object):
    def __init__(self, initval=None, name='var'):
        self.val = initval
        self.name = name

    def __get__(self, obj, objtype):
        return self.val


class Base(object):
    attr_1 = RevealAccess(10, 'var "x"')

    def __init__(self):
        self.attr_2 = RevealAccess(10, 'var "x"')


def main():
    b = Base()
    print("Access to class attribute, return: ", Base.attr_1)
    print("Access to instance attribute, return: ", b.attr_2)

if __name__ == '__main__':
    main()

Above will output:

('Access to class attribute, return: ', 10)
('Access to instance attribute, return: ', <__main__.RevealAccess object at 0x10184eb50>)

The same type of instance access through class or instance return different result!

And i found in c.PyObject_GenericGetAttr definition,and a great post.

Explain

If the attribute is found in the dictionary of the classes which make up. the objects MRO, then check to see if the attribute being looked up points to a Data Descriptor (which is nothing more that a class implementing both the __get__ and the __set__ methods). If it does, resolve the attribute lookup by calling the __get__ method of the Data Descriptor (lines 28–33).

Tchad answered 22/6, 2017 at 8:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.