Lesson 8: Introduction to classes
Subject: | Classes |
New concepts: | Classes, __init__- and __str__ methods |
Work procedure: | You are encouraged to discuss the work with others but should write your own code.
Try to solve the exercises before you look at the answers. Ask a teaching assistant if you don't understand the answers. |
Estimated working time: | 4 hours. |
Examination: | No mandatory examination. |
Classes
In previous lessons we've talked about objects of different kinds (e.g. turtles, lists, strings, tuples). In this lesson we'll describe how we can define our own kinds, our own classes, of objects.
An object can have both attribute (data) and methods. A turtle object for example has attributes such as position, direction, size, and color as well as methods. These methods are functions that fetches information about or changes a specific object (forward, left, ...).
A class can be described as a blueprint or description of what kind of data and methods the object should have.
Example 1: Dice
Say that we want to create a dice object. A dice has many attributes, e.g. color, size, material, number of sides, and value. Deciding what attributes to include depends on what we want to use the object for, in this case we want to use the dice objects in some kind of game context and limits ourselves to number of sides and value.
We want to be able to create multiple different dices with different number of sides.
We'll begin with declaring a dice class and creating two dice objects.
Code | Printout |
---|---|
class Dice:
pass
d1 = Dice()
d2 = Dice()
print(d1)
print(d2)
|
<__main__.Dice object at 0x10099d048>
<__main__.Dice object at 0x10099d0f0>
|
-
Classes are defined with the word
class
, followed by the name we want the class to have, as well as a colon. - The class name should start with a capital letter.
-
After this first row follows one or more indented statements with the class content.
In this case we haven't added any attributes or methods. We must still have an indented row in which
the key word
pass
is used, to show that the class is currently empty. -
The expression
Dice()
creates a dice object and returns a reference to it (compare to how we createTurtle
objects). - Although the printout seems cryptic it still suggests that we have two different dice objects, as they do have different addresses.
We have yet to add any attributes to the dice class. To do this the special method __init__
is used:
Code | Printout |
---|---|
class Dice:
def __init__(self, sides):
self.sides = sides
self.value = random.randint(1, self.sides)
d1 = Dice(6)
d2 = Dice(12)
print('d1: ', d1.sides, d1.value)
print('d2: ', d2.sides, d2.value)
|
d1: 6 1
d2: 12 10
|
-
Since the class now has some context we remove
pass
. -
The initiation method (often called "constructor") must be named
__init__
and haveself
as its first parameter. Afterself
we can add however many parameters we want. - Note the indentation!
-
The assignment statements
self.name = value
creates attributes with the given name and value. The constructor in this case creates two attributes, and assigns the value to one from a parameter, and to the a randomized value. -
Note that
sides
andself.sides
are two different things. The first is a parameter and the second is an attribute. Attributes are stored in the object and exists for as long as the object does, while the parameter exists only in the context of the constructor. By saving the parameter's value in an attribute the value is always availble in the object. The parameter could have been called something - anything - else, it would not affect the object. -
When we create objects (e.g.
Dice(6)
) the only argument we include is the ones we created. The parameterself
is added automatically.
The method __str__
In the printout in the example above we've had to explicitly fetch the values of the different attributes.
In this case there are only two, but it would be nice to have a standard way of creating a string from an object in case there are many more attributes.
We'd like to write e.g. print(d1)
and get a nice printout of the object.
To do this we use another special method: __str__
.
Code | Printout |
---|---|
class Dice:
def __init__(self, sides):
self.sides = sides
self.value = random.randint(1, self.sides)
def __str__(self):
return f'Sides: {self.sides:2d}, value: {self.value:2d}'
d1 = Dice(6)
d2 = Dice(12)
print('d1: ', d1)
print('d2: ', d2)
|
d1: Sides: 6, value: 6
d2: Sides: 12, value: 11
|
self
.
We need to use self.
in order to access the attributes. The string's content is completely up to us.
Methods of our own
The methods mentioned above had already defined names and meanings. However, we can also add our own methods to classes. In this case we'd like to add a methodroll
in order to roll the dice (that is, give in a new randomized number):
Code | Printout |
---|---|
class Dice:
def __init__(self, sides):
self.sides = sides
self.value = random.randint(1, self.sides)
def __str__(self):
return f'Sides: {self.sides:2d}, value: {self.value:2d}'
def roll(self):
self.value = random.randint(1, self.sides)
d1 = Dice(6)
d2 = Dice(12)
for i in range(5):
d1.roll()
d2.roll()
print(f'{d1.value:2d}, {d2.value:2d}')
|
6, 10
5, 12
5, 7
6, 3
3, 10
|
Example 2: Poker dice
In a game of dice poker typically 5 dice are used. One turn in the game means rolling all 5 dices. To represent a set of poker dice we can use e.g. a list of 5 dice objects. A class for a set of poker dice can then be written like this:Code | Printout |
---|---|
class PokerDice:
def __init__(self):
self.dice_list = []
for i in range(5):
self.dice_list.append(Dice(6))
def __str__(self):
return str(sorted([d.value for d in self.dice_list]))
def roll(self):
for d in self.dice_list:
d.roll() # Uses the roll method in Dice
print('Poker dice:')
pd = PokerDice()
for i in range(10):
pd.roll()
print(pd)
|
Poker dice:
[1, 3, 3, 3, 4]
[1, 1, 1, 2, 5]
[2, 4, 5, 5, 6]
[1, 1, 1, 3, 3]
[2, 2, 2, 5, 6]
[1, 3, 4, 6, 6]
[1, 2, 3, 4, 6]
[1, 3, 4, 6, 6]
[1, 2, 3, 5, 5]
[1, 1, 3, 4, 6]
|
- The initiation method creates a list of 5 6-sided dice objects.
-
The methods
__str__
creates a list with the dice values, sorts it, and returns it in form of a string. Note that we use list comprehension to create the return value. -
The method
roll
takes advantage of theroll
method in theDice
class.
Exercises
-
Sometimes we want to be able to choose a different number of dices. Modify the code so that the player can decide how many dice they want.
Only the init method needs to be changed:
def __init__(self, number_of_dice): self.dice_list = [] for i in range(self._number_of_dice): self.dice_list.append(Dice(6))
-
Write a method that returns the number of dice!
def number_of_dice(self): return len(self.dice_list)
-
The method
__str__
makes use of list comprehension. Rewrite the method so that it no longer does.res =[] for d in self.dice_list: res.append(d.value) return str(sorted(res))
-
Rewrite the
__init__
method so that it makes use of list comprehension.def __init__(self): self.dice_list = [Dice(6) for i in range(5)]
-
Create a class
Rectangle
. A rectangle should have a height, width, and position in the x-y plane. The class should contain the standard methods__init__
and__str__
, as well as a method that returns the rectangles surface area and a method that draws the rectangle using the Turtle package.import turtle class Rectangle: def __init__(self, width, height, xpos, ypos): self.width = width self.height = height self.xpos = xpos self.ypos = ypos def __str__(self): return f'Rectangle({self.width}, {self.height}, ' + \ f'{self.xpos}, {self.ypos})' def area(self): return self.width*self.height def draw(self): t = turtle.Turtle() t.penup() t.goto(self.xpos, self.ypos) t.pendown() for x in range(2): t.forward(self.width) t.left(90) t.forward(self.height) t.left(90) r = Rectangle(200, 100, 0, 0) print(r) r.draw()
The rectangle's position has been interpreted to be in the lower left corner.
Question
How many hours did you spend on this lesson?