Introduction
Python is an object-oriented programming language and therefore the emphasis is on objects. But what is an object?
An object is a fundamental building block: integers, strings, floats, dictionaries, are all objects and a class is a blueprint for that object.
Think of a class as a sketch of a human body. We generally draw details of the shapes such as legs, arms, face etc and based on these characteristics we build the final drawing of a human body. We can create many versions starting from the same initial sketch: every version of a human body is called an instance of a class and the process of creating this object is called instantiation.
Aim of this notebook
In this first part we will explore the basics of classes in Python, with emphasis on the difference between class and instance variables, when to use them and how.
Classes and instances
First of all, let’s see the syntax to create a class in python.
# Define a Pet class
class Pet():
def __init__(self, name, age):
self.name = name
self.age = age
# Create a class method that prints the Id of our Pet
def get_id(self):
print('My name is {} and I am {} yrs old'.format(self.name, self.age))
# my_pet is an instance of the Pet class
= Pet('Bob', 3)
my_pet my_pet.get_id()
My name is Bob and I am 1 yrs old
Let’s now define some aspects of terminology when looking at the Pet class.
my_pet
is an instance of the Pet class, with name
and age
referred as instance variables. Instance variables are variables that are owned by instances of the class.
__init__
or get_id
are referred to as methods of the Pet class. In the example, the __init__
method is called as soon as we create a new instance of the Pet class.
Methods receive the instance as first argument automatically, each time we create methods within a class. By convention, the way to call the instance is with self
. In get_id
we see that self
is used to retrieve the instance variables name
and age
.
Another way to visualize self
is by doing the following:
# my_pet.get_id() is the same as:
Pet.get_id(my_pet)
My name is Bob and I am 1 yrs old
As we can see, the above line of code behaves the same as calling my_pet.get_id()
. The only difference is that we are using the class Pet
, calling the get_id
method and passing the istance my_pet
.
Let’s now prove that name
and age
are indeed instance variables:
= Pet('Fido', 1)
my_second_pet
my_second_pet.get_id()
# my_pet still has its own name and age
my_pet.get_id()
My name is Fido and I am 3 yrs old
My name is Bob and I am 1 yrs old
As we can see, name
and age
are owned by each specific instance!
Class variables
Class variables are defined within the class construction and because they are owned by the class itself, they are shared by all instances of the class.
class Pet():
# Define a class variable that defines in which city my pets live
= 'Milan'
city_address
def __init__(self, name, age):
self.name = name
self.age = age
def get_id(self):
# I can access the class variable city_address either using Pet.city_address or with self.city_address
print('My name is {} and I am {} yrs old. I live in {}!'.format(self.name, self.age, self.city_address))
# Let's create two instances of the Pet class
= Pet('Bob', 3)
my_first_pet = Pet('Fido', 1)
my_second_pet
print(my_first_pet.city_address)
print(my_second_pet.city_address)
my_first_pet.get_id() my_second_pet.get_id()
Milan
Milan
My name is Bob and I am 3 yrs old. I live in Milan!
My name is Fido and I am 1 yrs old. I live in Milan!
As we can see, since Bob and Fido are my two pets and given that my pets live with me, it makes sense to have a shared class variable.
As written in the code comment above, I can access the class attribute city_address
either using Pet.city_address
or with self.city_address
. The latter might sound confusing, since we didn’t specify any self.city_address
in the __init__
method. What’s happening under the hood is that Python is first checking if instance contains the variable, if it doesn’t, it’ll check if the class does.
To better understand this, let’s check the instance namespace and the class one
print(my_first_pet.__dict__)
print(Pet.__dict__)
{'name': 'Bob', 'age': 3}
{'__module__': '__main__', 'city_address': 'Milan', '__init__': <function Pet.__init__ at 0x000001AE49139F70>, 'get_id': <function Pet.get_id at 0x000001AE491393A0>, '__dict__': <attribute '__dict__' of 'Pet' objects>, '__weakref__': <attribute '__weakref__' of 'Pet' objects>, '__doc__': None}
As we can see, the instance my_first_pet
doesn’t have the city_address
attribute, whereas the class does (together with other stuff we don’t care about now).
Let’s suppose now I change city where I live, moving to Rome.
= 'Rome'
Pet.city_address
# All the instances will inherit the new city_address.
print(my_first_pet.city_address)
print(my_second_pet.city_address)
my_first_pet.get_id() my_second_pet.get_id()
Rome
Rome
My name is Bob and I am 3 yrs old. I live in Rome!
My name is Fido and I am 1 yrs old. I live in Rome!
Let’s suppose now that Fido stays in Milan with my family, how can we change that? That’s easy, we simply create the attribute city_address
within the Fido
instance
= 'Milan'
my_second_pet.city_address
# Only Fido will have Milan
print(Pet.city_address)
print(my_first_pet.city_address)
print(my_second_pet.city_address)
my_first_pet.get_id() my_second_pet.get_id()
Rome
Rome
Milan
My name is Bob and I am 3 yrs old. I live in Rome!
My name is Fido and I am 1 yrs old. I live in Milan!
# This can be seen in
print(my_first_pet.__dict__)
print(my_second_pet.__dict__)
print(Pet.__dict__)
{'name': 'Bob', 'age': 3}
{'name': 'Fido', 'age': 1, 'city_address': 'Milan'}
{'__module__': '__main__', 'city_address': 'Rome', '__init__': <function Pet.__init__ at 0x000001AE49139F70>, 'get_id': <function Pet.get_id at 0x000001AE491393A0>, '__dict__': <attribute '__dict__' of 'Pet' objects>, '__weakref__': <attribute '__weakref__' of 'Pet' objects>, '__doc__': None}
All the above is happening because the we defined the get_id method using self.city_address
> print('My name is {} and I am {} yrs old. I live in {}!'.format(self.name, self.age, self.city_address))
Should it be always like that? What about Pet.city_address
?
As we saw, it is convenient to have the flexibility of being able to change the city in which the Pet lives, so it makes sense to leave self
in the method.
Let’s see what would happen if we changed to Pet.city_address
class Pet():
# Define a class variable that defines in which city my pets live
= 'Milan'
city_address
def __init__(self, name, age):
self.name = name
self.age = age
def get_id(self):
print('My name is {} and I am {} yrs old. I live in {}!'.format(self.name, self.age, Pet.city_address))
= Pet('Bob', 3)
my_first_pet = Pet('Fido', 1)
my_second_pet
my_first_pet.get_id()
my_second_pet.get_id()
= 'Rome'
Pet.city_address print('\nAFTER MOVING TO ROME')
my_first_pet.get_id()
my_second_pet.get_id()
print('\nTRYING TO CHANGE CITY TO FIDO, NOT WORKING!')
= 'Milan'
my_second_pet.city_address
my_first_pet.get_id() my_second_pet.get_id()
My name is Bob and I am 3 yrs old. I live in Milan!
My name is Fido and I am 1 yrs old. I live in Milan!
AFTER MOVING TO ROME
My name is Bob and I am 3 yrs old. I live in Rome!
My name is Fido and I am 1 yrs old. I live in Rome!
TRYING TO CHANGE CITY TO FIDO, NOT WORKING!
My name is Bob and I am 3 yrs old. I live in Rome!
My name is Fido and I am 1 yrs old. I live in Rome!
Since get_id works with Pet.city_address
, creating the attribute city_address = 'Milan'
within the instance my_second_pet
doesn’t affect the result, since we are still accessing the class variable!
When would it make sense to use a class variable then?
Why not counting the number of pets I own each time I create a new instance of the Pet class?
class Pet():
= 'Milan'
city_address = 0
n_pets
def __init__(self, name, age):
self.name = name
self.age = age
+= 1
Pet.n_pets
def get_id(self):
print('My name is {} and I am {} yrs old. I live in {}!'.format(self.name, self.age, self.city_address))
print('I own {} pets'.format(Pet.n_pets))
= Pet('Bob', 3)
my_first_pet = Pet('Fido', 1)
my_second_pet
print('I now own {} pets'.format(Pet.n_pets))
I own 0 pets
I now own 2 pets
Compared to self.city_address
, now it’s very unlikely that we are going to change the number of pets using an instance of the Pet class!
Conclusion
In this notebook we learned how to create a simple class and the difference between instance variables and class ones.
In Part 2, I’ll explore static methods and class methods in details.