Observera att denna sida är under utveckling!

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>
Notes:
  1. Classes are defined with the word class, followed by the name we want the class to have, as well as a colon.
  2. The class name should start with a capital letter.
  3. 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.
  4. The expression Dice() creates a dice object and returns a reference to it (compare to how we create Turtle objects).
  5. 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
Notes:
  1. Since the class now has some context we remove pass.
  2. The initiation method (often called "constructor") must be named __init__ and have self as its first parameter. After self we can add however many parameters we want.
  3. Note the indentation!
  4. 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.
  5. Note that sides and self.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.
  6. When we create objects (e.g. Dice(6)) the only argument we include is the ones we created. The parameter self 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
The method, that defined how an object should be expressed as a string, should only have the parameter 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 method roll 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
We can also use classes when creating new classes, as this next example shows.

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]
Notes:
  1. The initiation method creates a list of 5 6-sided dice objects.
  2. 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.
  3. The method roll takes advantage of the roll method in the Dice class.

Exercises

  1. 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. Answer
    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))
    
  2. Write a method that returns the number of dice! Answer
        def number_of_dice(self):
            return len(self.dice_list)
    
  3. The method __str__ makes use of list comprehension. Rewrite the method so that it no longer does. Answer
            res =[]
            for d in self.dice_list:
                res.append(d.value)
            return str(sorted(res))
    
  4. Rewrite the __init__ method so that it makes use of list comprehension. Answer
        def __init__(self):
            self.dice_list = [Dice(6) for i in range(5)]
    
  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. Answer
    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?


Valid CSS!