Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Class Attribute vs. Instance Attribute In Python: What You Might Have Missed

DZone 's Guide to

Class Attribute vs. Instance Attribute In Python: What You Might Have Missed

Being able to distinguish between these two attributes in your code is important. Read on learn how to properly use them in your app.

· Web Dev Zone ·
Free Resource

As an object-oriented language, Python provides two scopes for attributes: class attributes and instance attributes.

While the instance attribute in Python has exactly the same characteristics and definition as the other object-oriented languages, the class attribute is always mistakingly considered to be the exact equivalent of the static attribute in Java or C++. To be accurate, class attributes in Python and static attributes in Java or C++ have a lot in common, however, they have behavioral differences that I will highlight in this article.

Class Attribute vs. Instance Attribute

Let's start with the basics:

  • An instance attribute is a Python variable belonging to one, and only one, object. This variable is only accessible in the scope of this object and it is defined inside the constructor function, __init__(self,..) of the class.

  • A class attribute is a Python variable that belongs to a class rather than a particular object. It is shared between all the objects of this class and it is defined outside the constructor function, __init__(self,...), of the class.

The below ExampleClass is a basic Python class with two attributes: class_attr and instance_attr. 

class ExampleClass(object):
  class_attr = 0

  def __init__(self, instance_attr):
    self.instance_attr = instance_attr
  • instance_attr is an instance attribute defined inside the constructor.

  • class_attr  is a class attribute defined outside the constructor.

The instance_attr is only accessible from the scope of an object. The class attribute (class_attr) is accessible as both a property of the class and as a property of objects, as it is shared between all of them.

if __name__ == '__main__':

foo = ExampleClass(1)
bar = ExampleClass(2)

    # print the instance attribute of the object foo
    print (foo.istance_attr)
    #1
    #print the instance attribute of the object var
    print (bar.instance_attr)
    #2
    #print the class attribute of the class ExampleClass as a property of the class itself 
    print (ExampleClass.class_attr)
    #0
    #print the classattribute  of the class as a proporty of the objects foo,bar
    print (bar.class_attr)
    #0
    print (foo.class_attr)
    #0
    # try to print instance attribute as a class property
    print (ExampleClass.instance_attr)
    #AttributeError: type object 'ExampleClass' has no attribute 'instance_attr'

Notice that the class attribute can be accessed as a class property and as an instance property, however, accessing an instance attribute as a class property raises an AttributeError.

Behind the Scenes

Behind the scenes, it is a game of namespaces. If you have already read the Zen of Python, the last line states: "Namespaces are one honking great idea -- let's do more of those!" So what is a namespace?

In Python, a namespace is a mapping between objects and names. To keep it simple, let's say it is a Python dictionary that has as a key to the name of the object and its value as a value. Different namespaces can coexist with the property while the names within them are independent.

Python classes and objects have different namespaces,  for our example, we have ExampleClass.__dict__  as a namespace for our class and foo.__dict__(bar.__dict__)as a namespace for our object foo(bar).

if __name__ = '__main__':
 foo = ExampleClass(1)
    bar = ExampleClass(2)

    print str(ExampleClass.__dict__)
    #{'__module__': '__main__', 'class_attr': 0, '__dict__': <attribute '__dict__' of 'ExampleClass' objects>, '__weakref__': <attribute '__weakref__' of 'ExampleClass' objects>, '__doc__': None, '__init__': <function __init__ at 0x031192F0>}
    print str(foo.__dict__)
    #{'instance_attr': 1}
    print str(bar.__dict__)
    #{'instance_attr': 2}

When you access an attribute (instance or class attribute) as a property of an object using the dot convention, it searches first in the namespace of that object for that attribute name. If it is found, it returns the value, otherwise, it searches in the namespace of the class. If nothing is found there as well, it raises an  AttributeError. The object namespace is before the class namespace.

If we find, in one class, both an instance attribute and a class attribute with the same name, the access to that name from your object will get you the value in the object namespace. Below a simplified version of the lookup function.

def instlookup(inst, name):
    ## simplified algorithm...
    if inst.__dict__.has_key(name):
        return inst.__dict__[name]
    else:
        return inst.__class__.__dict__[name]

When you access an attribute as a class property, it searches directly in the class namespace for the name of that attribute. If it is found, it returns the value, otherwise, it raises an AttributeError

Class Attributes Mutate to Be Instance Attributes

Yeah, it might seem weird, but they do!

Let's consider the following scenario and then comment it together.

if __name__ = '__main__':
 foo = ExampleClass(1)
    bar = ExampleClass(2)

    #print the class attribute as a porperty of a foo
    print foo.class_attr
    #0
    #modify the class attribute as a foo property
    foo.class_attr = 5
    print foo.class_attr
    #5
    #print the class attribute as a porperty of a bar
    print bar.class_attr
    # 0 
    #oups !!!!

The  class_attr is shared between all the objects of the class. However, when we changed the value from the foo object, this change is not visible from the bar object which still has the old value,  0, rather than the new value, 5! Hold on... let's check our namespaces and try to understand what the hell is happening.

if __name__ = '__main__':
 foo = ExampleClass(1)
    bar = ExampleClass(2)


    print str(foo.__dict__)
    #{'instance_attr': 1} the namespace of the foo object had only his instance attribute
    print foo.class_attr 
    #0
    foo.class_attr = 5
    print str(foo.__dict__)
    '''{'instance_attr': 1, 'class_attr': 5} once the affectation is done 
    on the class attribute from the object foo , it is added as an instance attribute 
    in his namespace
    ''' 

The affectation added a new instance attribute to the object foo and only to that object which is why in the previous example the object bar kept printing the class attribute.

With immutable objects, this behavior is always the same. However, with mutable objects like lists, for example, it is not always the case, depending on how you modify your class attribute.

Let's change our previous class to have a list as a class attribute.

class ExampleClass(object):
  class_attr = []

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

We modify that list as a property of the object foo by appending a new element to it.

if __name__ = '__main__':
 foo = ExampleClass(1)
    bar = ExampleClass(2)

    #print the class attribute as a porperty of a foo
    print foo.class_attr
    # []
    #modify the class attribute as a foo property
    foo.class_attr.append(0)
    print foo.class_attr
    #[0]
    #print the class attribute as a porperty of a bar
    print bar.class_attr
    # [0] 

When a mutable class attribute is modified by an object, it does not mutateto turn into an instance attribute for that object. It stays shared between all the objects of the class with the new elements appended to it.

However, if you attach a new list to that attribute ( foo.class_attr = list("foo")) you will get the same behavior as the immutable objects. 

if __name__ = '__main__':
 foo = ExampleClass(1)
    bar = ExampleClass(2)

    #print the class attribute as a porperty of a foo
    print foo.class_attr
    # []
    #modify the class attribute as a foo property
    foo.class_attr = list("example") 
    print foo.class_attr
    #[e,x,a,m,p,l,e]
    #print the class attribute as a porperty of a bar
    print bar.class_attr
    # [] 

You can compare the namespaces by yourself as proof of the previous behavior.

Conclusion

Python class attributes may be useful in different cases, however, they must be used with caution in order to avoid unexpected behaviors.

Topics:
object and class ,open source ,python tutorial ,web dev ,object-oriented programming

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}