Class instances

Class instances

vvrubel

Track content

By now, you already know what classes are and how they're created and used in Python. Now let's get into the details about class instances.

A class instance is an object of the class. If, for example, there was a class River, we could create such instances as Volga, Seine, and Nile. They would all have the same structure and share all class attributes defined within the class River.

However, initially, all instances of the class would be identical to one another. Most of the time that is not what we want. To customize the initial state of an instance, the __init__ method is used.

$1. def __init__()

The __init__ method is a constructor. Constructors are a concept from the object-oriented programming. A class can have one and only one constructor. If __init__ is defined within a class, it is automatically invoked when we create a new class instance. Take our class River as an example:

class River:
    # list of all rivers
    all_rivers = []
    
    def __init__(self, name, length):
        self.name = name
        self.length = length
        # add current river to the list of all rivers
        River.all_rivers.append(self)

volga = River("Volga", 3530)
seine = River("Seine", 776)
nile = River("Nile", 6852)

# print all river names
for river in River.all_rivers:
    print(river.name)
# Output:
# Volga
# Seine
# Nile

We created three instances (or objects) of the class River: volgaseine, and nile. Since we defined name and length parameters for the __init__, they must be explicitly passed when creating new instances. So something like volga = River() would cause an error.

The __init__ method specifies what attributes we want the instances of our class to have from the very beginning. In our example, they are name and length.

$2. self

You may have noticed that our __init__ method had another argument besides name and length: self. The self argument represents a particular instance of the class and allows us to access its attributes and methods. In the example with __init__, we basically create attributes for the particular instance and assign the values of method arguments to them. It is important to use the self parameter inside the method if we want to save the values of the instance for later use.

Most of the time we also need to write the self parameter in other methods because when the method is called the first argument that is passed to the method is the object itself. Let's add a method to our River class and see how it works. The syntax of the methods is not of importance at the moment, just pay attention to the use of the self:

class River:
    all_rivers = []

    def __init__(self, name, length):
        self.name = name
        self.length = length
        River.all_rivers.append(self)

    def get_info(self):
        print("The length of the {0} is {1} km".format(self.name, self.length))

Now if we call this method with the objects we've created we will get this:

volga.get_info()
# The length of the Volga is 3530 km
seine.get_info()
# The length of the Seine is 776 km
nile.get_info()
# The length of the Nile is 6852 km

As you can see, for each object the get_info() method printed its particular values and that was possible because we used the self keyword in the method.

Note that when we actually call an object's method we don't write the self argument in the brackets. The self parameter (that represents a particular instance of the class) is passed to the instance method implicitly when it is called. So there are actually two ways to call an instance method: self.method() or class.method(self). In our example it would look like this:

# self.method()
volga.get_info()
# The length of the Volga is 3530 km

# class.method(self)
River.get_info(volga)
# The length of the Volga is 3530 km

$3. Instance attributes

Classes in Python have two types of attributes: class attributes and instance attributes. You should already know what class attributes are so here we'll focus on the instance attributes. Instance attributes are defined within methods and they store instance-specific information.

In the class River, the attributes name and length are instance attributes since they are defined within a method (__init__) and have self before them. Usually, instance attributes are created within the __init__ method since it's the constructor, but you can define instance attributes in other methods as well. However, it's not recommended so we advise you to stick to the __init__.

Instance attributes are available only from the scope of the object which is why this code will produce a mistake:

print(River.name)  # AttributeError

Instance attributes, naturally, are used to distinguish objects: their values are different for different instances.

volga.name  # "Volga"
seine.name  # "Seine"
nile.name   # "Nile"

So when deciding which attributes to choose in your program, you should first decide whether you want it to store values unique to each object of the class or, on the contrary, the ones shared by all instances.

$4. Summary

In this topic, you've learned about class instances.

If classes are an abstraction, a template for similar objects, a class instance is a sort of example of that class, a particular object that follows the structure outlined in the class. In your program, you can create as many objects of your class as you need.

To create objects with different initial states, classes have a constructor __init__ that allows us to define necessary parameters. Reference to a particular instance within methods is done through the self keyword. Within the __init__ method, we define instance attributes that are different for all instances.

Most of the time in our programs we'll deal not with the classes directly, but rather with their instances, so knowing how to create them and work with them is very important!


Class vs instance

By now, you already know that Python makes a distinction between class attributes and instance attributes. If you recall, class attributes are the ones shared by all instances of the class, while instance attributes are specific for each instance. Moreover, class attributes are defined within the class but outside any methods, and instance attributes are usually defined within methods, notably the __init__ method.

Now let's go over the difference between class attributes and instance attributes in more detail.

$1. Changing attributes

Suppose we have a class Pet:

class Pet:
    kind = "mammal"
    n_pets = 0  # number of pets
    pet_names = []  # list of names of all pets

    def __init__(self, spec, name):
        self.spec = spec
        self.name = name
        self.legs = 4

This class has three class attributes, kindn_pets, and pet_names, as well as three instance attributes, defined in the __init__ method, specname, and legs.

Let's create instances of that class and see how changing class or instance attributes works in Python:

tom = Pet("cat", "Tom")
avocado = Pet("dog", "Avocado")
ben = Pet("goldfish", "Benjamin")

We've created three instances of the class Pet that have the same class attributes and different instance attributes. Now, it would make sense to change the value of n_pets because we now have more than 0 pets. Since n_pets is an integer, which is an immutable type, we can change its value for the whole class only if we access it directly as a class attribute:

# access class attribute directly through the class
Pet.n_pets += 3

Pet.n_pets      # 3
tom.n_pets      # 3
avocado.n_pets  # 3
ben.n_pets      # 3

If we tried to change the value of n_pets via the instances it would not work as we wished:

# access class attribute through instances
tom.n_pets += 1
avocado.n_pets += 1
ben.n_pets += 1

Pet.n_pets      # 0
tom.n_pets      # 1
avocado.n_pets  # 1
ben.n_pets      # 1

Even though all instances have access to the class attribute, if those attributes are immutable, changing their value for one instance doesn't change them for the whole class.

The same would be with the attribute kind, since strings are also immutable in Python. If we change it for the object ben (since a goldfish is not a mammal), it would stay the same for other attributes (as it should):

ben.kind = "fish"

Pet.kind      # "mammal"
tom.kind      # "mammal"
avocado.kind  # "mammal"
ben.kind      # "fish"

In cases where there's a handful of unique objects that need to have a different value of the class variable, this is totally fine. However, if there're a lot of those objects, you should consider making this attribute an instance attribute!

The situation with the pet_names attribute is different. The pet_names attribute is a list and, therefore, mutable, so changes to it affect the whole class. See below:

tom.pet_names.append(tom.name)
avocado.pet_names.append(avocado.name)
ben.pet_names.append(ben.name)

Pet.pet_names      # ["Tom", "Avocado", "Benjamin"]
tom.pet_names      # ["Tom", "Avocado", "Benjamin"]
avocado.pet_names  # ["Tom", "Avocado", "Benjamin"]
ben.pet_names      # ["Tom", "Avocado", "Benjamin"]

If for some reason, we wanted the class attribute pet_names to store different values for different instances, we could do that by creating a new list instead of appending to the existing one:

tom.pet_names = ["Tom"]
avocado.pet_names = ["Avocado"]
ben.pet_names = ["Benjamin"]

Pet.pet_names      # []
tom.pet_names      # ["Tom"]
avocado.pet_names  # ["Avocado"]
ben.pet_names      # ["Benjamin"]

But this doesn't seem very convenient or necessary: after all, this is a class attribute and the idea behind it is that it stores values common to all the instances. So, again, if you want some attribute to store unique values, make it an instance attribute!

Another way to look at this situation is in terms of (re)assignment. =+=, and similar operators are assignment operators. When we try to modify the class attribute from the instance using these operators, we essentially create a new instance attribute for that particular object. This is why other instances and the class itself are unaffected by this change — because we assigned some value to a newly created "instance attribute". Adding a new element to the list with append, for example, is different because there is no reassignment happening, we just modify the existing list.

Like, for example, variable legs. It is an instance attribute, even though it is not explicitly passed as an argument of the __init__ method. The default value is 4, but we can change it if we ever need to. That would be helpful for the object ben because the fish doesn't have legs (they do have fins, but let's save the question of whether a fin can be considered a leg in this context for another time). This is how we change the value of legs for the object ben:

ben.legs = 0

And that's it! There are basically no tricky moments with changing instance attributes because, again, they affect just one object.

$2. Adding attributes

In addition to changing attributes, we can also create attributes for the class or a particular instance. For example, we want to see the information about the species of all our pets. We then could write it in the class itself from the very beginning, or we could create a variable like this:

Pet.all_specs = [tom.spec, avocado.spec, ben.spec]

tom.all_specs      # ["cat", "dog", "goldfish"]
avocado.all_specs  # ["cat", "dog", "goldfish"]
ben.all_specs      # ["cat", "dog", "goldfish"]

Another thing we could do is to create an attribute for a specific instance. For example, we want to remember the breed of the dog called Avocado. Breeds are usually relevant in the context of dogs (cats have breeds too, but they are not that different) so it makes sense that we would want only our dog to have that information:

avocado.breed = "corgi"

Here we created an attribute breed for the object avocado and assigned a value corgi to it. Other instances of the class Pet as well as the class itself wouldn't have this attribute, so the following lines of code would cause an error:

Pet.breed  # AttributeError
tom.breed  # AttributeError
ben.breed  # AttributeError

$3. Summary

In this topic, we've shown the differences in using class attributes and instance attributes.

Class attributes are used to store information available for all instances of the class, but using them may be tricky if we don't take into account the type of the variable.

So, in what cases would we want to use class attributes? Well, firstly, if we want to define default values for all objects. Secondly, to store necessary class-specific constants (mathematical, for example). And lastly, to keep tabs on the data of all objects like in the example with pet_names. You may want to have easy access to particular information of every instance of your class and in that case, you could use a mutable class attribute.

Remember that how the values of class attributes change depends on whether they are mutable or not. Take that into account when writing your program and operating the objects of the class!

Instance attributes, on the other hand, store information that is different for every instance, and it is obviously their main function. Changing and adding new instance attributes may only affect a single object, but still, pay attention to the alterations that you make.

We hope you now know the difference between the class and instance attributes and you'll use them successfully in your programs!



Report Page