Classes and objects¶
Classes and Object-Oriented-Programming¶
Objects and classes are a way to group up a bunch of function and variables into a single "thing". When you get all the way down to it, this is simply a way of organizing everything into groups that make sense. There are benefits down the road for making things easier to understand, debug, extend, or maintain, but basically it is just a way to make code easy to understand and develop.
This might sound very abstract, but in practice it is not: everything is an object in Python!
int
, dict
, list
... etc. are all different types of objects.
To be more precise, int
, dict
, list
... are known as classes, and objects are particular instances
of such classes. In the next example:
int
would be the class, and a
would be the object (or instance, they are used interchangeably). So,
in other words, classes are the blueprint from which objects are created.
Note
It is often helpful to make analogies of classes with animals: for instance, if dog
is a class,
then Max = dog()
(one dog in particular) is the instance.
Making use of classes in programming is what is known as Object-Oriented-Programming (OOP). OOP is highly concerned with code organization, reusability, and encapsulation. OOP is partially in contrast to Functional Programming, which is a different paradigm used a lot in Python. Not everyone who programs in Python uses OOP.
Note
So, should we use functions or classes when programming? As a general rule of thumb, we can think of classes/objects as nouns and functions/methods as verbs. In other words, functions do specific things, classes are specific things. With a language like Python we are not forced to choose: we can use just a little bit of each, when it is most convenient.
Defining classes¶
Python makes it very easy to define our own classes. A very basic class would look something like this:
Note
Variables in classes are also known as attributes, and functions in classes are also known as methods. These words are complete synonyms.
We'll explain why you have to include that self
as a parameter a little bit later.
First, to assign the above class to an object you would do the following:
class MyClass:
variable = "blah"
def function(self):
print("This is a message inside the class.")
myobjectx = MyClass()
Now the variable myobjectx
holds an object of the class MyClass
that contains the variable
and the function defined within the class called MyClass
.
Accessing Object Variables
Accessing object variables¶
To access the variable inside the newly created object myobjectx
you would do the following:
class MyClass:
variable = "blah"
def function(self):
print("This is a message inside the class.")
myobjectx = MyClass()
myobjectx.variable
So for instance the below would output the string blah
:
class MyClass:
variable = "blah"
def function(self):
print("This is a message inside the class.")
myobjectx = MyClass()
print(myobjectx.variable)
You can create multiple different objects that are of the same class (i.e., have the same variables and functions
defined). However, each object contains independent copies of the variables defined in the class. For instance,
if we were to define another object with the MyClass
class and then change the string in the variable above:
class MyClass:
variable = "blah"
def function(self):
print("This is a message inside the class.")
myobjectx = MyClass()
myobjecty = MyClass()
myobjecty.variable = "yackity"
# Then print out both values
print(myobjectx.variable)
print(myobjecty.variable)
Accessing object functions¶
To access a function inside of an object you use notation similar to accessing a variable:
class MyClass:
variable = "blah"
def function(self):
print("This is a message inside the class.")
myobjectx = MyClass()
myobjectx.function()
The above would print out the message This is a message inside the class.
The meaning of self
¶
The built-in self
is used by a class to refer to a specific instantiation of the class.
This is easier seen with an example. Consider this class:
and now compare it to:
The idea of the function set_name
is clear: we want to give a name to our object of the class Dog
.
So, what's the difference between both cases? In the first case, we are defining dog_name
inside of the
function set_name
, but when we leave the function this "inside" variable disappears. Hence, the function
has no effect.
Note
Actually, the first function would not work for reasons a bit more obscure, but that doesn't matter for now. See this if you're interested in reading more aaa.
What we want to do is access the particular dog instance, and set a name to that instance. This is the
purpose of self
: it means "our current instance".
__init__()
function¶
The __init__()
function is a special function that is called when the class is being instantiated.
__init__
doesn't initialize a class, it initializes an instance of a class or an object.
The difference between variables assigned inside the __init__()
method versus variables assigned
in the class definition is that in the first case we are only defining values for one particular
instance, whereas if we define a value in the class definition, all objects from that class will have
the same value.
Note
In other words, each dog has colour, but dogs as a class don't: hence the color for one particular dog
should be defined inside the __init__()
method.
The class is a concept of an object. When you see Fido and Spot, you recognise their similarity, their doghood. That's the class. However, when you say
class Dog:
def __init__(self, legs, colour):
self.legs = legs
self.colour = colour
fido = Dog(4, "brown")
spot = Dog(3, "mostly yellow")
You're saying, Fido is a brown dog with 4 legs while Spot is a bit of a cripple and is mostly yellow.
Class inheritance¶
Inheritance is a mechanism that allows you to create a hierarchy of classes that share a set of properties and methods by deriving a class from another class. This is extremely useful because it allows us to write less code thanks to code reuse.
For instance, imagine that we need to define a class for a dogs and cats (and potentially many more). Instead of
writing everything every time for each other animal that we define, we could make them inherit from another parent
class Animal
:
class Animal:
def __init__(self, age, height):
self.age = age
self.height = height
def print_height(self):
print(self.height)
class Dog(Animal):
def __init__(self, age, height, race):
super().__init__(age, height)
self.race = race
class Cat(Animal):
def __init__(self, age, height, meowness):
super().__init__(age, height)
self.meowness = meowness
my_cat = Cat(age=2, height=35, meowness=0.5)
some_dog = Dog(age=1, height=50, race="beagle")
my_cat.print_height()
print(some_dog.race)
# Output
35
beagle
In this example, Dog
and Cat
inherit from the class Animal
. This means that all
Animal
methods and attributes are also available to Dog
and Cat
as if they were defined
inside their definitions.
Note
In the previous code snippet you might notice the use of the super()
function.
super()
is a built-in function returns a proxy that allows us to access methods of the base class.
We use it to refer to the base class: in the previous example, we are using it to call the Animal.__init__
function from the Dog
and Cat
__init__
functions.