How to use Classes in Python

May 6, 2020
Day 4 of 101 Days of Python
So, in searching Reddit today for some interesting Python problems, I came across several questions about the use of Classes in Python.
Why use classes?
I started learning Python and there is a concept that I just can't seem to understand: Classes.
I don't understand how they work (including the hierarchy inside a class) nor why they are used.
Can someone explain??
Trying to understand where and how to use Classes
I started learning Python a while ago, making random simple projects, and doing some codewars. Then I read about OOP and how it's a "better" way to program? (If that's the correct way to put it)
I watched the first 3 videos of Corey Schafer's series but I'm still honestly clueless right now, do I have to make my code just a bunch of classes? are normal functions that we define outside a class not "OOP"? Is OOP always better or should I sometimes stick to writing code "normally"?
Also, what are some projects/challenges/ideas I can practice OOP with?
I have no idea what I'm talking about so my questions probably sound really stupid, but please help.
We will answer the "Why" and the "How" questions separately.
The Why
As a motivating examle we are going to build some classes for a videogame classic. If you were to re-program Super Mario from scratch, you might want to create the blocks he jumps on, the evil enemies that he needs to smash, and each level that he has to run through. All of these things will be created multiple times while you play the game, and you don't want to have to type out a unique instance of each one.
What do I mean by this?
Well, lets just focus on the evil enemies that Mario must smash. Two common enemies are the Mushroom and the Ghost. If you want to create a Super Mario level with 100 mushrooms that Mario must face, you don't want to type out 100 individual mushrooms. It's much easier to just create a blueprint for a mushroom, and use this blueprint 100 times to create the 100 mushrooms that you will need in the level.
So lets learn how to build a class, the we will demonstrate why we use classes by using a single class to build 100 mushrooms, instead of type out 100 mushrooms!
The How
How to build a class in Python is a fairly straightforward topic. I'm going to build up some classes for the Mushroom and the Ghost, one step at a time, adding more code as we cover new parts of a class.
The most important keyword is the class keyword. You just use this keyword followed by the name of your class to begin creating a new class. You also have to put something in the class for Python to recognize it as a valid definition. To start I'll just put the None keyword, which means that the class does nothing.
class Mushroom: None
The next step in creating a class is to build a constructor. A constructor is a function that is called when your class is used to create an instance of the class, known as an object. Python has a special way to define a class constructor - which is to create a function using the standard def keyword, and naming the constructor function __init__.
Adding a constructor, your class will look something like this.
class Mushroom: def __init__(self): None
Notice that I moved the None into the constructor, so that it is a function that takes one argument, but does not actually do anything.
What is that self argument doing?
Inside of a class, all functions take as their first argument a reference to the class itself. So that if you want a function to call another funciton in the class, or access a variable member of the class you can use the dot opeartor on self. Again, this requirement only applies to functions inside of classes, not standalone functions that you might create in a script.
Lets have this function do something. We know that we want our mushroom to walk, and be alive or stomped, so lets have the constructor initialize those two members in the class each time a new Mushroom object is created.
class Mushroom: def __init__(self): self.is_alive = True self.walking_direction = "left"
Now each time you want to create a new Mushroom, you just call Mushroom(), and you will get back a new Mushroom object that is alive and walking left.
Next we need some ways to change the state of the Mushroom later, either when Mario stomps on it or when it changes direction. We can add two more functions that take the self keywords as arguments, and update the relevant class fields.
class Mushroom: def __init__(self): self.is_alive = True self.walking_direction = "left" def change_walking_direction(self): if self.walking_direction == "left": self.walking_direction = "right" else: self.walking_direction = "left" def stomp_mushroom(self): self.is_alive = False
There is one more topic to cover here, which is class Inheritance.
Inheritance is when a class copies functionality over from another class. This applies to anything inside of a class.
Imagine that you wanted to build other evil enemies for Mario other than just a Mushroom. They may not all walk left or right, buy they will all either be alive or not. So we can have the Mushroom class inherit the capability to be alive or not from another class.
class Enemy: is_alive = True class Mushroom(Enemy): def __init__(self): self.walking_direction = "left" def change_walking_direction(self): if self.walking_direction == "left": self.walking_direction = "right" else: self.walking_direction = "left" def stomp_mushroom(self): self.is_alive = False class Ghost(Enemy): def __init__(self): self.floating_direction = "up" def change_floating_direction(self): if self.floating_direction == "up": self.floating_direction = "down" else: self.floating_direction = "up" def stomp_ghost(self): self.is_alive = False
Now we have created a Mushroom class and Ghost class, both of which have the field is_alive available in them. Go ahead and try this yourself in Python, build the above and try Mushroom().is_alive and Ghost().is_alive. You will see that the behavior is available, and is is copied from the Enemy class. Again, this copy process from one class to another is called Inheritance.
The Why
Most commonly, you want to use a class in Python in 2 scenarios:
(1) You are going to model / create / represent something in code that will be created or used multiple times, or
(2) You want to create an encapsulated environment
You want to remember that a class is a blueprint for something to be created in your code in the future.
Lets see how this works with a version of our Mushroom class minus the inheritance:
class Mushroom: def __init__(self): self.is_mushroom_alive = True self.walking_direction = "left" def change_walking_direction(self): if self.walking_direction == "left": self.walking_direction = "right" else: self.walking_direction = "left" def stomp_mushroom(self): is_mushroom_alive = False
In the code above, we have created a Mushroom class / blueprint for creating a new mushroom. There are a few parts here to focus on. the __init__ function is the constructor function, this will be called when we create a new mushroom. This makes sure that each new mushroom is marked as alive and walking left when it is created.
There are still two things that can happen to the mushroom as it walks. It can hit a wall, and so it needs to turn around, or it can get stomped by Mario, in which case the mushroom is no longer alive.
We created two functions that can be called on the mushroom when this happens.
Lets see this class in action. Suppose that while you are coding your game, you want to create 100 new mushroom enemies when a level starts. Now that we have a class, we can easily create 100 mushrooms. Let's setup a function to create n number of mushrooms, then call this in the toplevel.
# Create a list of mushroom enemies def create_mushrooms(n): mushrooms = [] for i in range(n): mushrooms.append(Mushroom()) return mushrooms # I'm only creating 5 mushrooms here to make it easy to read, but you can try with 100 >>> all_mushrooms = create_mushrooms(5) >>> all_mushrooms [<day_4.Mushroom object at 0x7f305630ebe0>, <day_4.Mushroom object at 0x7f305630ec18>, <day_4.Mushroom object at 0x7f305630ec50>, <day_4.Mushroom object at 0x7f305630ec88>, <day_4.Mushroom object at 0x7f305630ecc0>]
This gives us back a list of mushrooms, all of which are currently alive and walking left. You can see that since we had the class / blueprint / instructions for how to create a mushroom, we can use it to create mushrooms over and over. This saves us time because we no longer have to write code for mushrooms, can you imagine coding that over and over for each mushroom that you needed!
Another benefit of this is that each mushroom object contains the state of the mushroom (i.e. is each mushroom alive, and which direction is is walking).
# All mushrooms are alive >>> print(list(map(lambda x: x.is_mushroom_alive, all_mushrooms))) [True, True, True, True, True]
And so if Mario stomps the first 3 mushrooms, the last two are still alive, and storing the is_mushroom_alive variable in the class allows us to keep track of each mushroom separately!
>>> all_mushrooms[0].stomp_mushroom() >>> all_mushrooms[1].stomp_mushroom() >>> all_mushrooms[2].stomp_mushroom() >>> print(list(map(lambda x: x.is_mushroom_alive, all_mushrooms))) [False, False, False, True, True]
The directions can also be changed independently as they walk into walls and need to walk in the other direction.
# All mushrooms start out walking left >>> print(list(map(lambda x: x.walking_direction, all_mushrooms))) ['left', 'left', 'left', 'left', 'left'] # Some change direction all_mushrooms[0].change_walking_direction() all_mushrooms[2].change_walking_direction() all_mushrooms[4].change_walking_direction() >>> print(list(map(lambda x: x.walking_direction, all_mushrooms))) ['right', 'left', 'right', 'left', 'right']
To conclude, I hope that this not only shows you how to use classes, but also why you want to use classes to save yourself time. You can imagine that with a few more classes you could represent an entire video game with just a handful of objects, even though there may be thousands of things happenning over the course of a game. This is the power of objects.