Fork/Clone Ch.10 Classes Jedi Training
Fork/Clone Ch.10 Classes Jedi Training
Classes, Objects and Methods are very powerful programming tools. They make programming easier. In fact, you are already familiar with the concept of classes and objects. A class is a "classification" of an object. Think of a class as the "blueprint" of how to build a house. The blueprint is the class. The house is the object. Just as you can make several houses from one blueprint, you can create several objects from one class. An object is called an "instance" of a class. When we create an object from a class it is called "instantiation". Objects have attributes, such as a person's name, height, and age. Objects also have methods (functions). Methods define what an object can do, like run, jump, or sit.
Each character in an adventure game needs data: a name, location, strength, are they raising their arm, what direction they are headed, etc. Plus those characters do things. They run, jump, hit, and talk. Without classes, our Python code to store this data might look like:
name = "Obi Wan Kenobi"
status = "Jedi Master"
strength = 100
midichlorian_count = 50
In order to do anything with this character, we would need to pass that data to a function:
def display_character(name,status,strength,midichlorian_count):
print(name,status,strength,midichlorian_count)
display_character(name,status,strength,midichlorian_count)
Obi Wan Kenobi Jedi Master 100, 50
Now imagine creating a program that has a set of variables like that for each Jedi Knight and Sith Lord in our game. Then we need to create functions that work with those items. The data gets out of hand very quickly and it will get even worse! As our game expands, we may need to add new fields to describe our character such as home:
name = "Obi Wan Kenobi"
status = "Jedi Master"
strength = 100
midichlorian_count = 50
home = "Tatooine"
def display_character(name,status,strength,midichlorian_count,home):
print(name,status,strength,midichlorian_count, home)
display_character(name,status,strength,midichlorian_count,home)
Obi Wan Kenobi good 100, 50, Tatooine
In the example above, there is only one function. But in a large project, we might have hundreds of functions that deal with the main character. Adding a new field to help describe what that character has and can do would require us to go through each one of those functions and add it to the parameter list. That would be a lot of work. And perhaps we need to add parameters to different types of characters like droids. Classes help alleviate this problem.
A better way to manage multiple data attributes is to define a structure that has all of the information. Then we can give that "grouping" of information a name, like Character. This can be easily done in Python and any other modern language by using a class. For example, we can define a class representing a character in a game:
class Character( ):
def __init__(self):
self.name = ""
self.status = ""
self.strength = 0
self.midichlorian_count = 0
self.home = ""
In the code above, Character is the class name. The variables in the class, such as name and status, are called attributes or fields. (Note the similarities and differences between declaring a class and declaring a function.)
Unlike functions and variables, class names should begin with an upper case letter. While it is possible to begin a class with a lower case letter, it is not considered good practice.
The def __init__(self) is a special function called a constructor that is run automatically when the class is called and an object is created. We'll discuss the constructor further down the page.
The self is kind of like the pronoun my. When inside the class Character we are talking about my name, my status, etc. We don't want to use self outside of the class definition for Character, to refer to a Character field. Why? Because just like the pronoun "my," it means someone totally different when said by a different person! self is only used while an object of the class is being created, not after the object is created. By the way, self is just standard use for Python; my or this or whatever you want could also be used. I will use self in any Class examples however.
The class code defines a class but it does not actually create an instance of one. The code told the computer what fields a character has and what the initial default values will be. We don't actually have a character yet though. We can define a class without creating one just like we can define a function without calling it. We have a "blueprint" of a character but we have not yet created one! To create an object of our Character class and set the fields, look at the example below:
# Create a Character
jedi_1 = Character()
# Set the fields in the Character
jedi_1.name = "Obi Wan Kenobi"
jedi_1.status = "Jedi Master"
jedi_1.strength = 100
jedi_1.midichlorian_count = 20
jedi_1.home = "Tatooine"
An instance of the Character class is created in line 2. Note how the class Character name is used, followed by parentheses. The variable name can be anything that follows normal naming rules. jedi_1 is the first "object" we've made from the Character class. This process is called instantiation.
To set the fields in the class, a program must use the dot operator. This operator is the period that is between the jedi_1 and the field name. See how lines 5-8 use the dot operator to set each field value.
A very common mistake when working with classes is to forget to specify which instance of the class you want to work with. If only one Character is created, it is natural to assume the computer will know to use that address you are talking about. This is not the case however. Since I named the first object or instance of the class as jedi_1 I need to use it with the dot operator to set the field for that particular instance.
Let's create another object or instance of the Character class:
# Create a Character
jedi_2 = Character( )
# Set the fields in the Character
jedi_2.name = "Yoda"
jedi_2.status = "Jedi Master"
jedi_2.strength = 200
jedi_2.midichlorian_count = 50
jedi_2.home = "Dagobah"
print("The first object created was " + jedi_1.name)
print("The second object created was " + jedi_2.name)
The first object created was Obi Wan Kenobi
The second object created was Yoda
In addition to attributes, classes may have methods. A method is a function that exists inside of a class. Expanding the earlier example of a Character class, the code below adds a method for the Character fighting.
class Character( ):
def __init__(self):
self.name = ""
self.status = ""
self.strength = 0
self.midichlorian_count = 0
self.home = ""
def fight(self):
print("Lightsaber on!, says " + self.name)
Method definitions in a class look almost exactly like function definitions. The big difference is the addition of a parameter self. The first parameter of any method in a class must be self. This parameter is required even if the function does not use it.
Here are the important items to keep in mind when creating methods for classes:
Attributes should be listed first, methods after.
The first parameter of any method must be self.
Method definitions are indented exactly one tab stop.
Methods may be called in a manner similar to referencing attributes from an object. See the example code below.
sithlord_1 = Character( )
sithlord_1.name = "Darth Vader"
sithlord_1.fight( )
Lightsaber on!, says Darth Vader
When we create an object of Character, by default the Character has no name. We have been naming it after we have created it. It would be nice if we could name the Character the exact moment that we instantiate the object. Python classes have a special initialization function that is called any time an instance of that class is created. By adding this function called a constructor, a programmer can add code that is automatically run each time an instance of the class is created. Let's look at our Character class code again.
class Character( ):
def __init__(self):
self.name = ""
self.status = ""
self.strength = 0
self.midichlorian_count = 0
self.home = ""
def fight(self):
print("Lightsaber on!, says " + self.name)
The constructor starts on line 2. It must be named init with two underscores before the init, and two underscores after. A common mistake is to only use one.
The constructor must take in self as the first parameter just like other methods in a class. When a Character object is created, the init function is automatically called and the message is printed to the screen.
A constructor can be used for initializing and setting data for the object. You can't use the constructor more than once in a Class definition.
You might have noticed that the Character class above still allows the name attribute to be left blank after the creation of the Character object. How do we keep this from happening? Many objects need to have values right when they are created. The constructor function can be used to make this happen. See how we've modified the Character class below to auto-set several attributes the moment the object is instantiated.
class Character( ):
def __init__(self,new_name,new_status,my_home):
self.name = new_name
self.status = new_status
self.strength = 0
self.midichlorian_count = 0
self.home = my_home
def fight(self):
print("Lightsaber on!, says " + self.name+","+self.status+"from"+self.home)
The constructor function now has some additional parameters called new_name, new_status and my_home. The values of these parameters are used to set several attributes in the Character class for example where it says self.name = new_name. It is no longer possible to create a Character class without a name, status or home. A common mistake is to name the parameter of the init function the same as the attribute and assume that the values will automatically synchronize. This does not happen. You MUST set the initializing attribute equal to the parameter of the init function. Let's instantiate a new Character object and pass in the three parameters. Remember all three of these parameters are strings so they need to be in quotations.
jedi = Character("Luke Skywalker","Jedi Master","Tatooine")
jedi.fight()
Lightsaber on!, says Luke Skywalker, Jedi Master from Tatooine
What happens now when you try to create an object of the Character class without providing one of these parameters? Try it out.
We can no longer instantiate an object of the Character class without providing one of these parameters. The error tells us that the constructor takes 4 arguments but we gave only 3. In fact, it looks like we gave only 2!!!! Remember the first argument is always self which automatically gets passed to the constructor.
Another powerful feature of using classes and objects is the ability to make use of inheritance. It is possible to create a class and inherit all of the attributes and methods of a parent class. For example perhaps we decide that some of the characters in our game will be called droids and they will have all of the attributes of the Character class except they need to beep. We can create a new Droid class and inherit all the attributes and methods from the Character class. THAT'S AWESOME!
class Droid(Character):
def beep(self):
print("Beep, says "+ self.name)
new_droid = Droid("R2D2")
new_droid.beep( )
Beep, says R2D2
In the code below we will make our parent class Character and then two children classes called Jedi and Sith which will inherit both the initialized name and the fight method from the Character class.
What if we want to add a new attribute to each of the children's classes but keep the name attribute from the parent Character class? We will write a child constructor in each class to bring in our new attribute but the first line in the child's constructor will be to call in the parent's constructor using the keyword super() as seen on lines 10 and 18. If the child did not need any extra attributes there would be no need to create a child constructor.
In the Jedi class we again created a fight() method. We don't want to override the parent's fight() method so we called it in using super() on line 13. This will first print out the parent's fight() method and then the rest of the new fight() method on line 14.
In the Sith class we also created a fight() method. This overrides the parent's fight() method because we did not use super() to call in the parent's fight() method.
So in review, classes have two main types of relationships. They are “is a” and “has a” relationships.
A parent class should always be a more general, abstract version of the child class. This type of child to parent relationship is called an 'is a' relationship. For example, a parent class Animal could have a child class Dog. The dog is an animal. The Dog class could have a child class Poodle. The poodle is a dog, and is an animal.
It does not work the other way! A dolphin is a mammal, but a mammal is not always a dolphin. So the class Dolphin should never be a parent to a class Mammal.
Unrelated items that do not pass the is a test should not form parent/child relationships. For example, a class Table should not be a parent to a class Chair because a chair is not a table.
The other type of relationship is the 'has a' relationship. These relationships are implemented in code by class attributes. A dog has a name, and so the Dog class has an attribute for name. Likewise a person could have a dog, and that would be implemented by having the Person class have an attribute for Dog. The Person class would not derive from Dog because that would be some kind of insult.