Lesson 3 - RollingDie in Python - Constructors and random numbers
In the previous lesson, First object-oriented app in Python - Hello object world, we programmed our first object-oriented application in Python. We are already able to create new classes with attributes and parameterized methods with return values. Today, we're going to start working on an arena in which two warriors will battle against each other. The battle will be turn-based and one warrior will decrease the health points of other one based on his attack and on the other warrior's defense. Essentially, we'll be simulating a board game, so we'll add a rolling "die" to our game so as to add a bit of randomness. Let's start it easy and create this rolling die today. We're also going to learn to declare custom constructors.
We'll create a new file named Arena
. We'll create a new class
there and name it RollingDie
. Let's think about the attributes
we'll have to add to our die in order for it to function according to our
current needs. We should be able to choose the number of sides. Typically 6 or
10 sides as is standard in this type of game. Our die will also need a random
number generator. Python provides the random
module, which includes
the randint()
method for this sort of projects. Our class will now
have a sides_count
attribute.
Last time, for simplicity's sake, we set all the attributes of our class as
publicly accessible; however, in most cases, we don't want our attributes to be
modified externally. In this case, we mark them as private. Private attributes
start with two underscores __
and are only accessible from the
inside of the class. When we design a class we usually set everything as private
and then we make public only those class members we really need to expose. Our
class should now look something like this, we're going to add the attributes in
a minute:
class RollingDie: """ Class representing a die for a board game """
Сonstructors
We'd now only be able to set values of the public attributes from outside of the class since the private attributes are not visible from the outside. This problem can be bypassed by name mangling, but it's not a good practice since it would violate encapsulation. We've already discussed constructors a bit. A constructor is a method that is called while an object instance is being created. We use it to set the internal state of the object and perform any necessary initialization. We'd create a die in the program now like this:
die = RollingDie()
The RollingDie()
method is the constructor. Since our class
doesn't have a constructor, Python automatically generated an empty method.
However, we'll now add a constructor to the class. We declare it as a method. In
Python, we can use two methods for this purpose. The __new__()
method and __init__()
. The first one is called when the object is
being created, but we'll usually get by with the second method which is called
when the object is being initialized. Our constructor method will be empty. Of
course, we'll write self
as the first parameter and insert the
pass
keyword to the method body, telling Python that it shouldn't
do anything. If pass
wasn't there, Python would expect a command
block and report an error:
def __init__(self): pass
If we just created a regular variable in the __init__()
method,
it'd stop existing once the method ends. But we need to create a
sides_count
attribute. We create object attributes using the
powerful self
keyword. self
is followed by a period
.
and the attribute name. So let's create a public
sides_count
class attribute:
def __init__(self): self.sides_count = 6
If we create a new die now, it'll have the sides_count
attribute
set to 6
. Let's print the number of sides to the console, so as to
visually confirm that the value is there. The full code looks like this:
class RollingDie: """ Class representing a die for a board game """ def __init__(self): self.sides_count = 6 die = RollingDie() print(die.sides_count) input()
It's not a good idea to set the attribute as public since we don't want somebody to be able to change the number of sides once the die is created.
We'll add a get_sides_count()
method to the class returning the
value of the sides_count
attribute and we'll make this attribute
private. We have now successfully created a read-only attribute. Meaning that,
the attribute is not visible and can only be read by using the
get_sides_count()
method, so it can't be changed from outside the
class. Python has other constructs made for these purposes, but we'll go over
them later. The updated class with the method would look something like
this:
class RollingDie: """ Class representing a die for a board game """ def __init__(self): self.__sides_count = 6 def get_sides_count(self): """ Returns the number of sides the die has """ return self.__sides_count die = RollingDie() print(die.get_sides_count()) input()
The attribute became private by adding two underscores. We also changed the printing because we can now only get the value of the attribute by calling the method.
The output:
Console application
6
Now we have visual confirmation that the constructor had been called. However, we'd like to be able to specify how many sides our die will have. Let's add a parameter to our constructor:
def __init__(self, sides_count): self.__sides_count = sides_count
Notice that the attribute and parameter names are almost the same. Even if we
had the sides_count
as a public attribute, it won't be a problem.
We use self
the specify that the variable on the left side belongs
to the instance and the right one is understood by Python as the value from the
parameter. With a public attribute, the situation would look like this:
def __init__(self, sides_count): self.sides_count = sides_count
But let's go back to our original code and pass a value for this parameter to the constructor:
die = RollingDie(10) # a constructor with a par. 10 is called print(die.get_sides_count()) input()
The output:
Console application
10
Everything works as expected. Python will not generate an empty, aka
parameterless, constructor now, so we can't create a die without passing the
parameter it needs. We can make it possible by specifying the default value for
the sides_count
parameter in the constructor declaration. We'll set
the value to 6
since the user probably expects a die to have this
value as default:
def __init__(self, sides_count = 6): self.__sides_count = sides_count
Let's create 2 dice instances now, one with the parameter specified and one without it:
sixSided = RollingDie() tenSided = RollingDie(10) # or we can write RollingDie(sides_count=10) print(sixSided.get_sides_count()) print(tenSided.get_sides_count()) input()
The output:
Console application
6
10
Thanks to the keyword argument, we don't have to specify the sides count. We
can use such arguments with other methods as well, not just with constructors.
Many Python functions and methods offer keyword arguments, e.g. the build-in
print()
function. It's a good idea to look at the methods'
declaration and arguments, so you don't end up programming something what has
already been done for you. Now we got a constructor which allows us to create
different rolling dice. Let's continue.
Random numbers
Next, we'll define a roll()
method in the
RollingDie
class, which will return a random number from
1
to the number of sides. The method won't have any parameters. We
can obtain a random number using the random
module. We'll import it
internally using one underscore _
. And we'll use its
randint()
method:
def roll(self): """ Rolls a die and returns a number from 1 to the sides count """ import random as _random return _random.randint(1, self.__sides_count)
When importing modules, Python looks at whether the module has already been imported, so if it was previously imported, Python will not import it again. Thus, the first line in the method is performed only once.
Overriding the __str__() method
Our RollingDie
class is almost ready, let's add one more method
which we'll use a lot in the future. We're now talking about the
__str__()
method contained in every object,
including our die. The method is designed to return a textual
representation of the instance. It's handy in all cases where we need
to print the instance or work with it as with text. Even numerical datatypes
have this method.
Python performs implicit conversions. When we want to print a number or any
other object to the console, Python calls the __str__()
method and
prints its return value. When creating a class, we should consider whether
__str__()
will come in handy. We should never make our own method
such as print()
when there is already a way to handle string
conversions in Python. It makes no sense to use it in our die, but when
implementing warriors it could return their names. Still, for education's sake,
we'll override __str__()
in our RollingDie
class
anyway. It'll say that it's a die and print how many sides it has.
First, let's try to print a RollingDie
instance directly to the
console as it is now:
print(sixSided)
Only the path to our class is printed to the console. Although the method is already declared, we can easily declare it again. It this case, we say we override it:
def __str__(self): """ Returns a textual representation of our die """ return str("A rolling die with {0} sides".format(self.__sides_count))
Let's print the die instance to the console again.
The output:
Console application
A rolling die with 6 sides
Let's test our rolling dice now. We'll roll them in loops and see if they work as expected. Let's modify the end of the file:
# Creates the dice sixSided = RollingDie() tenSided = RollingDie(10) # Rolls the 6-sided die print(sixSided) for _ in range(10): print(sixSided.roll(), end=" ") # Rolls the 10-sided die print("\n", tenSided, sep="") for _ in range(10): print(tenSided.roll(), end=" ") input()
The output should look something like this:
Console application
A rolling die with 6 sides
3 6 6 1 6 3 6 2 6 3
A rolling die with 10 sides
5 9 9 2 10 4 9 3 10 5
We've created a nice, customizable class that represents a rolling die. It'll come in handy when we get to the arena part of the application, but you can use it whenever you'd like. Hopefully, you now understand how OOP allows you to reuse components. In the next lesson, Object References, Cloning, and Garbage Collector in Python, we'll focus on how the objects are handled in memory.
Did you have a problem with anything? Download the sample application below and compare it with your project, you will find the error easily.
Download
By downloading the following file, you agree to the license terms
Downloaded 57x (1.81 kB)
Application includes source codes in language Python