Skip to content

Commit 152222c

Browse files
committed
Add basic classes and objects
1 parent e6acbd4 commit 152222c

31 files changed

+486
-13
lines changed

09 Classes and Objects.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
parent: Programming for Modelling and Data Analysis
3+
has_children: true
4+
nav_order: 9
5+
---
6+
7+
# Classes and Objects
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
---
2+
parent: Classes and Objects
3+
grand_parent: Programming for Modelling and Data Analysis
4+
nav_order: 1
5+
---
6+
7+
# A Problem
8+
9+
## Let's play 21
10+
11+
Imagine a game of 21. Its rules are very simple:
12+
13+
1. A player starts with score 0.
14+
2. He/she rolls a dice to increase the score. The number on the dice adds to the current score.
15+
3. After rolling the dice, the player may decide to end the game with the current score or roll again.
16+
4. The goal is to collect as high score as possible. However, if in any moment the total score exceeds 21 (i.e. becomes 22 or higher), the player immediately loses with the final score 0.
17+
18+
Play this game with your friend. You can play in turns, changing active player after each roll, or first the first player rolls as many times as wishes, then the other one. You may also play with a larger number of players.
19+
20+
Now implement this game in Python. Start with a version for a single player. You need to keep track of the player's current score. It is also beneficial to track whether a player is still in a game.
21+
22+
A simple and ugly program may look like this:
23+
24+
```python
25+
import random
26+
27+
final_score = 0
28+
in_game = True
29+
30+
while in_game:
31+
dice = random.randint(1, 6)
32+
final_score += dice
33+
print(f"You roll {dice}. Your current score is {final_score}.")
34+
if final_score > 21:
35+
final_score = 0
36+
in_game = False
37+
else:
38+
while True:
39+
answer = input("Do you want to keep rolling? [y/n]: ").lower()
40+
if answer in 'yn':
41+
break
42+
print("You must answer 'y' or 'n'!")
43+
if answer == 'n':
44+
in_game = False
45+
46+
print(f"Your final score is {final_score}!")
47+
```
48+
49+
## What is wrong with this code?
50+
51+
The above code is simple. However, it has two significant problems. One is the lack of separation of the user interaction and logic i.e. keeping track of your score, deciding when to finish etc. (remember [Kitchen and Dining Room](../00%20Algorithms/3%20Frontend-backend)). You may somehow improve it by organizing parts of your code into functions:
52+
53+
```python
54+
import random
55+
56+
final_score = 0
57+
in_game = True
58+
59+
60+
def roll():
61+
global final_score, in_game
62+
dice = random.randint(1, 6)
63+
final_score += dice
64+
score = final_score
65+
if score > 21:
66+
final_score = 0
67+
in_game = False
68+
return dice, score # this return values are used only for printing messages
69+
70+
71+
def ask():
72+
while True:
73+
answer = input("Do you want to keep rolling? [y/n]: ").lower()
74+
if answer in 'yn':
75+
break
76+
print("You must answer 'y' or 'n'!")
77+
return answer == 'y'
78+
79+
80+
while in_game:
81+
dice, score = roll()
82+
print(f"You roll {dice}. Your current score is {score}.")
83+
if in_game:
84+
in_game = ask()
85+
86+
print(f"Your final score is {final_score}!")
87+
```
88+
89+
The other problem... well make it a game for two or three! You need to track the score of each player and whether a particular player is still in a game. Seems not easy, doesn't it? One reason is using global variables e.g. in the `roll()` function, which are necessary to update the game state between its calls (we cannot use local variables for this purpose, as they will be lost once we leave the function).
90+
91+
You may keep a score and playing state of each player in lists (one for scores, second for playing states, and yet another for player names), however, it would not be very elegant solution: you will also have to pass the player number to the `roll()` function. Another possibility would be to store player data in a dictionary and pass it to the roll function:
92+
93+
```python
94+
alfred = {'name': 'Alfred', 'in_game': True, 'final_score': 0}
95+
96+
def roll(player):
97+
dice = random.randint(1, 6)
98+
player['final_score'] += dice
99+
score = player['final_score']
100+
if score > 21:
101+
player['final_score'] = 0
102+
player['in_game'] = False
103+
return dice, score
104+
```
105+
106+
This seems better, however, it is still far from elegant.
107+
108+
**Please don't try any of these approaches!** Read the next chapter, to see how to solve the problem the right way.
109+
110+
<hr/>
111+
112+
Published under [Creative Commons Attribution-NonCommercial-ShareAlike](https://creativecommons.org/licenses/by-nc-sa/4.0/) license.
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
---
2+
parent: Classes and Objects
3+
grand_parent: Programming for Modelling and Data Analysis
4+
nav_order: 2
5+
---
6+
7+
# Classes and objects to organize your code
8+
9+
The problem described in the previous chapter can be summarized as follows: how to combine together all the variables describing each player's state and how to operate on them. Python has neat solution for this using a concept of classes and objects. Classes are general concepts of an entity, its state and what it can do (in our case this will be **a** player), while objects are specific instances of these concepts (**the** players: *Alfred*, *Beatrice*, *Charlie*).
10+
11+
It is best illustrated in example. In Python you define a class using **`class`** keyword:
12+
13+
```python
14+
class Player:
15+
16+
def __init__(self, name):
17+
self.name = name
18+
self.score = 0
19+
self.in_game = True
20+
21+
def roll(self):
22+
dice = random.randint(1, 6)
23+
self.score += dice
24+
score = self.score # `score` and `self.score` are DIFFERENT THINGS
25+
if score > 21:
26+
self.score = 0
27+
in_game = False
28+
return dice, score
29+
```
30+
31+
What you see above is a declaration of a class named `Player` (usually class names should start with a CapitalLetter), are two functions defined **inside a class** (in an indented block). Such functions in a class are called *methods*: I will use this term from now on. Methods in a class take one additional argument called `self`, which must be always the first argument. This argument represents a particular instance (object) we are operating on. We can use it to set and read object *attributes*, using `self.attribute_name` notation. Contrary to the local variables, the attributes are shared between the calls of the methods and may also be accessed from outside. On the other hand, unlike global variables, they can be different for different objects (i.e. we track e.g. `score` separately for *Alfred* and separately for *Beatrice*).
32+
33+
I guess, you understand the role of the `roll` method. `__init__` is the special method name, used for initialization of objects (it will be called automatically). Here, we set the player's name to the value provided as argument `name` (this a different thing than the attribute `self.name`) and initialize the score (`self.score`) and playing status (`self.in_game`).
34+
35+
To create particular instances of a class, we call it like a function:
36+
37+
```python
38+
player1 = Player("Alfred")
39+
player2 = Player("Beatrice")
40+
```
41+
42+
In the parenthesis we put the arguments that must be passed to the `__init__` method, **skipping the first argument `self`**.
43+
44+
Other methods are called using notation, you have already seen: `object.method(arguments_without_self)`:
45+
46+
```python
47+
player1.roll()
48+
player2.roll()
49+
```
50+
51+
For creating objects and calling methods, all the normal rules of passing arguments by name or using default arguments are the same as for normal functions. The only difference you must remember is to skip the first `self` argument. This argument is passed automatically and contains the object before the dot (or a newly created one in case of `__init__`).
52+
53+
You use the dot-notation, you may access not only methods, but also object attributes:
54+
55+
```python
56+
print(player1.score)
57+
```
58+
59+
## Two-player game
60+
61+
Using classes and objects it is now very easy to write a two-player game:
62+
63+
```python
64+
import random
65+
66+
67+
class Player:
68+
def __init__(self, name):
69+
self.name = name
70+
self.score = 0
71+
self.in_game = True
72+
73+
def roll(self):
74+
dice = random.randint(1, 6)
75+
self.score += dice
76+
score = self.score # `score` and `self.score` are DIFFERENT THINGS
77+
if score > 21:
78+
self.score = 0
79+
self.in_game = False
80+
return dice, score
81+
82+
83+
def ask(player):
84+
# This function is part on the input-output,
85+
# so it should not be in the `Player` class
86+
while True:
87+
answer = input(f"{player.name}, do you want to keep rolling? [y/n]: ").lower()
88+
if answer in 'yn':
89+
break
90+
print("You must answer 'y' or 'n'!")
91+
return answer == 'y'
92+
93+
94+
player1 = Player(input("Enter first player's name: "))
95+
player2 = Player(input("Enter second player's name: "))
96+
97+
while player1.in_game or player2.in_game:
98+
for player in (player1, player2):
99+
if player.in_game:
100+
dice, score = player.roll()
101+
print(f"{player.name} roll {dice} and has total score {score}.")
102+
# We check the condition again, as it might have changed in `player.roll()`
103+
if player.in_game:
104+
player.in_game = ask(player)
105+
106+
print(f"{player1.name}'s final score is {player1.score}!")
107+
print(f"{player2.name}'s final score is {player2.score}!")
108+
```
109+
110+
You may download the game code [from here](game21.py). Can you write this game to allow any number of players?
111+
112+
## Conclusions
113+
114+
This tutorial went through creating classes, instantiating objects, initializing attributes with the constructor method, and working with more than one object of the same class.
115+
116+
Object-oriented programming is an important concept to understand because it makes code recycling more straightforward, as objects created for one program can be used in another. Object-oriented programs also make for better program design since complex programs are difficult to write and require careful planning, and this in turn makes it less work to maintain the program over time.
117+
118+
119+
<hr/>
120+
121+
Published under [Creative Commons Attribution-NonCommercial-ShareAlike](https://creativecommons.org/licenses/by-nc-sa/4.0/) license.

0 commit comments

Comments
 (0)