We are still doing our Python evening courses. Today we are learning more about classes. Which seems appropriate for teachers. We have been looking at how to create and manage data in program objects.
It turns out that Python is very laissez-faire when it comes to object construction (just like it is about most things). As a stalwart C# developer I find this all a bit scary. In C# you have to tell the compiler everything about the design of an object that you might like to create. In other words:
public string Name;
public int Score;
This is a C# class that I could create to hold details of a player of a game (perhaps golf or cricket). Each instance of the class will hold the name of the player and their score. The C# compiler has been told to store a string in the Name property and an integer in the Score. So I can create objects that represent the fact that "Fred" has scored 100 runs, or whatever. And C# will make sure that the value of Name is only ever a string and the value of Score is only ever an integer. I like this a lot. It means that it is very hard for me to get my name and score storage into a mess. If I try to store a string where a number should be, for example put the value "Twenty Five" into the Score property, then my program won't even get to run.
In Python you can add things to any object at any time and the system pays no attention to the type of the values until a wheel comes off during program execution.
print('Nobody here but us chickens')
This Python code defines a Player class that just contains a single method that prints the message 'Nobody here but us chickens' when it is called. Fair enough, but not terribly useful. I can create an instance of the class if I want:
p = Player()
These two statements create an instance of the Player class (referred to by p) and then ask it to print its message. Fair enough. If I want to store data in the class I can just set some attributes, and they are added automatically when the program runs.
p.name = 'Fred'
Now my instance of Player holds a couple of attributes, the name and the score of that player. I can go on and add lots of other attributes if I like. The problem is that I could end up in a mess. Suppose that I decide to mark one particular player as the captain of the team. I can add an attribute to denote this:
p.isCaptain = True
Now I can tell which one of the players is the captain, right? Well, yes and no. But mostly no and always dangerous. I might now have a situation where only one player (the one that I have marked as captain) has the isCaptain attribute. The rest of the players in my system don't have the attribute set to false, they just don't have the attribute. This will become a problem if my program ever does something like this:
This statement cause the program to crash if it ever encounters a player without the isCaptain attribute. A C# program would not allow this to happen, since you would be unable to create a player that didn't have the isCaptain attribute.
Python programs are easier and quicker to write because you don't have to tell the compiler everything up front, but the penalty is that they are vulnerable to errors that C# programs can't ever have. My way of fixing this is to make a constructor method for player that allows you to set the initial values there:
def __init__(self, name, score):
self.name = "" + name
self.score = 0 + score
self.isCaptain = False
The __init__ method (yes, there are two underscores before and after the word init) is given the name and the score values and sets them into the player. It also sets a default value for the isCaptain property. I can now construct a player and set up the attributes in one statement:
p = Player('Fred', 99)
You may be wondering why I have statements like:
self.score = 0 + score
This is the trickery bit. I'm setting the score of the player to whatever the user of my constructor sent in. They could do something stupid like:
p = Player('Fred', 'har har')
In this case the score value is being delivered as a silly string, which is not what I want. But because the constructor tries to add this parameter to the integer value 0 this means that the program will fail and stop with the message:
self.score = 0 + score
TypeError: unsupported operand type(s) for +: 'int' and 'str'
I could write extra code to check for the type and raise my own exception, but this seems somehow simpler. I'm using this in all my Python constructors now. Maybe there's a better way of doing it, but I don't care because my way works for me.
Number one son reckons that surviving in languages like Python, which are great fun but inherently dodgy, is just a matter of coding standards. This has now become one of mine.