Class, instance and their attributes

Paul Manot
5 min readJan 11, 2021

What’s a Class and what’s an instance?

So in Object Oriented Programming (OOP) a class is a programming construct that allows you to create data structures (object) that have two things, a state and a behavior. By state we mean that the object can store some data that is specific to itself. By behavior we mean that the object has access to a common set of functionalities (called methods and not functions, because they are defined on an object). The canonical example is to compare classes to factories and objects to what comes outside of the factories. In this analogy if we have a Car class then the instances of the class also simply called objects are the cars. And a car like any object as a state that is unique. It has 5 or 3 doors. It’s red, pink or black. It has 4 wheels, leather seats or just synthetic ones… It also has some behavior. It can drive. It can honk. It can wipe the windshield, you name it! You get the idea.

And so what we call the state of an object in Python is represented by the data carried inside its attributes. The behavior is represented by the methods defined in the class and accessible by each instances of the class.

Let’s look at a simple example:

 1 class Car:                                                                    
2 pass
3
4 toyota = Car()
5 volvo = Car()
6 toyota.wheels = 4
7 volvo.wheels = 4
8 print(toyota is volvo) # prints False

So Here we have a class that does nothing but we were able to declare attributes to the 2 instances of the class Car. We gave the same value to the attribute wheels and yet the 2 objects created are different…

But attributes can be shared accross all instances not just added manually like in the above example. Let’s look into this further.

What’s an instance attribute?

Well as its name suggest a instance attribute relates to the instance itself and is unique to a single instance. let’s add a few attributes to our Car class definition.

  1 class Car:                                                                    
2 """ A Car class with an __init__ method"""
3
4 def __init__(self, make, model, doors):
5
6 self.make = make
7 self.model = model
8 self.doors = doors
9
10 car_1 = Car('toyota', 'verso', 5)

So what are we doing here?

We have added to our original Car class an __init__() method which is a default method that gets automatically called when we create an instance of a Car. __init__() takes a special argument as its first argument called self by convention. We don’t need to pass this argument when we create an instance as it refers automatically to the object. Note how we call the attributes on the object from line 6 to 8 the same way we were doing in our original example, only this time we are using the variable self to represent the object.

let’s create a few more cars and print some attribute values:

11 car_2 = Car('volvo', 'v40', 5)
12 car_3 = Car('Ferrari', 'f40', 3)
13 print(car_1.make)
14 print(car_2.make)
15 print(car_2.make)

And we get:

toyota
volvo
ferrari

We can see that each instances have their own attribute values as per the initialization. We access the value by calling the attribute on the object as we do on lines 13, 14 and 15. No restriction were put on the attributes (i.e we didn’t make them private by using the special notation __attribute_name) therefore they are accessible by dot notation on the object. Note that if the attribute couldn’t be found at the instance level the python interpreter would look at the class level if the attribute was present. But we will talk about that in the next section.

What’s a class attribute?

Class attributes are attributes defined at the class level. They will be shared by all the instances. Usually the kind of data stored is at a macro level. In other words information relevant when considering all the instances and not a single instance in isolation.

Let’s tweak our Car class. It is not interesting for a single car (one instance of the Car class) to know how many cars have been instantiated since the creation of the class itself, yet it’s relevant to keep that information at the class level, hence the class attribute count

  1 class Car:                                                                    
2 count = 0
3
4 def __init__(self, make, model, doors):
5 Car.count += 1 # There is a better way of doing this ;)
6 self.make = make
7 self.model = model
8 self.doors = doors
9
10 car_1 = Car('toyota', 'verso', 5)
11 car_2 = Car('volvo', 'v40', 5)
12 car_3 = Car('Ferrari', 'f40', 3)
13
14 print(Car.count)
15 print(car_1.count)
16 print(car_2.count)
17 print(car_3.count)

Which will give us:

3
3
3
3

Note how we called the attribute directly on the class and how we used the class directly to increment the count (line 5). We could have used type(self) instead of Car. This is more flexible but less clear. Also, as mentioned earlier, we were able to access the count class attribute through all the instances.

Let’s do something sneaky to better explain what’s happening

 10 car_1 = Car('toyota', 'verso', 5)
11 car_2 = Car('volvo', 'v40', 5)
12 car_3 = Car('Ferrari', 'f40', 3)
13
14 print(Car.count)
15 print(car_1.count)
16 print(car_2.count)
17 print(car_3.count)
18 car_2.count = 47
19 print(car_1.count)
20 print(car_2.count)
21 print(car_3.count)

And now we get:

3
3
3
3
3
47
3

Indeed a count instance attribute was created on the car_2 instance that’s shadowing the class attribute (line 18). Now at line 20 the look up which always start at the instance will find a count attribute defined for that instance only, hence the different result of 47.

An easy way to track the current attributes of an object is to call the __dict__ method which will give a list of key, value pairs called a dictionary in Python, hence the name.

let’s take car_2 :

 22 print(car_2.__dict__)

And we get:

3
3
3
3
3
47
3
{'make': 'volvo', 'model': 'v40', 'doors': 5, 'count': 47}

Note that we can do the same at the class level and we would get

{'__module__': '__main__', 'count': 3, '__init__': <function Car.__init__ at 0x7facd024ba60>, '__dict__': <attribute '__dict__' of 'Car' objects>, '__weakref__': <attribute '__weakref__' of 'Car' objects>, '__doc__': None}

If you ignore all the built-in attributes you can seen are count attribute with an unchanged value of 3.

To conclude we can say that attributes are an easy and powerful way to store data.

--

--