Davin Lawrence

A Crash Course in Classes

Classes and objects are an important concept in computer programming. They allow programmers to describe an object or concept from the real world and bring it into their programs. Whenever my students get to the section on classes, I find many are confused in both the how and why. Hopefully, this (yet another) class tutorial will help clear up confusion on both of these issues. I will use python to describe classes, but the concepts presented here can be used in other programming languages as well.

Classes are a way for programmers to define objects. Classes contain attributes and methods, in other words, data and ways to manipulate or interpret that data. An object can be anything, but it is easiest to think about something from the physical world. Lets use a pencil as a quick example. A pencil has several attributes. It may have a lead weight, it may be mechanical, it may have a crappy eraser, or it may be out of lead. The pencil would also have a few methods, such as write() or refillLead(). A quick class definition for a pencil in python may look like this:

class Pencil:
    def __init__(self):
        self.type = "mechanical"
        self.lead_count = 4
        self.lead_type = 0.7

    def write(self):
        self.lead_count -= 0.1

    def refillLead(self, amount):
        self.lead_count += amount

This class doesn’t do much, but it is a good first exposure to the Class concept. If we wanted to make an instance of this pencil, we could do so with the following code

pentel = Pencil()

We could then access the methods by putting a . after the variable name and adding the method name after. This probably looks very familiar to you if you have used methods for different objects in python before.

print(f"Lead count before writing: {pentel.lead_count}")
pentel.write()
print(f"Lead count after writing: {pentel.lead_count}")
Lead count before writing: 4
Lead count after writing: 3.9

RPG Character

One of my favorite examples when teaching classes is the object of a character in a role-playing game. They are ideal candidates, since every character in a RPG has similar base attributes and methods. Every player in our game will have the strength, intelligence, and dexterity attributes. We will also need a method to retrieve these values so we can compare two characters should they engage in battle. Finally, the character object is a great introduction to inheritance, since the character could have different attributes depending on if it was a mage, warrior, or bard.

Attributes and the Constructor

The best place to start when designing our class is to think about what attributes the object has. We have already listed the strength, dexterity, and intelligence attributes, but it would also be nice to give our character a name.

class RPGCharacter:
    def __init__(self, name):
        self.__name = name
        self.__strength = 10
        self.__intelligence = 10
        self.__dexterity = 10

There are few things to unpack in this code snippet. the __init__(self) function is the constructor for a class in python. It is typical to initialize all of the attributes for the class within the constructor. Whenever you call the class, the constructor is run to initialize the object and set up anything that is defined within the method. Currently, our constructor only sets our attributes to a base level and not much else. It also takes a name externally and sets our object’s name to this variable. A constructor can be as simple or as complex as you need and will vary depending on your application.

The keyword self is ubiquitous throughout the example, and it will be find in almost every class method you declare. The self keyword denotes that an attribute belongs to this class and is unique to that class. The double underscore denotes that the attribute is a private attribute. This attribute cannot be accessed from outside of the class. Looking back at our pencil example, notice that we can do this:

pentel = Pencil()
print(pentel.lead_count)
4

Whenever an attribute is made private, however, this is no longer valid code:

tom = RPGCharacter("Tom")
print(tom.__name)
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-6-91c70f9967b0> in <module>
      1 tom = RPGCharacter("Tom")
----> 2 print(tom.__name)


AttributeError: 'RPGCharacter' object has no attribute '__name'

This is an extremely useful property of classes, because there are several reasons as to why you would want to restrict access to the attributes. For example, what is preventing me from setting lead_count in the Pencil class to a string? What prevents me from setting it to a negative number? If the attribute is public, nothing will prevent me from doing this and other methods are now in danger of failing:

pentel.lead_count = "sandwich"
pentel.refillLead(3)
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-7-552372b89309> in <module>
      1 pentel.lead_count = "sandwich"
----> 2 pentel.refillLead(3)


<ipython-input-1-9a2a7fd36256> in refillLead(self, amount)
      9
     10     def refillLead(self, amount):
---> 11         self.lead_count += amount


TypeError: can only concatenate str (not "int") to str

Accessor Methods

Since we will need to extract the data from the object at some point in time, we can define accessor methods, sometimes called getter methods, to retrieve the data from the object. This gives us a secure way to get the data and perhaps format it in a way that desire.

class RPGCharacter:
    def __init__(self, name):
        self.__name = name
        self.__strength = 10
        self.__intelligence = 10
        self.__dexterity = 10

    def getStrength(self):
        return self.__strength

    def getIntelligence(self):
        return self.__intelligence

    def getDexterity(self):
        return self.__dexterity

    def getName(self):
        return self.__name.capitalize()

Notice the capitalize() method for a string in the getName() method we defined. This guarantees that no matter what, the first letter will be capitalized regardless of what was input by the user.

tom = RPGCharacter("tom")
print(f"Name: {tom.getName()}")
print(f"Strength: {tom.getStrength()}")
Name: Tom
Strength: 10

The sole point of an accessor method is to return the data contained within the class and are generally the quickest, simplest code contained within a class.

Mutator Methods

Our characters would not be very useful to our game if we did not have a way to work on the data contained within each character. For example, we would most likely want to level up our character after a fight or some great feat. We may also wish to allow a player to change the name of their character throughout the game. To do this, we create mutator methods, which are sometimes called setter methods. Mutator methods are very important as they are what makes our classes useful.

class RPGCharacter:
    def __init__(self, name):
        try:
            name = name.split()
            for i in range(len(name)):
                name[i] = name[i].capitalize()
            self.__name = ' '.join(name)
        except AttributeError:
            print("Name not set! Incorrect data type")
        self.__strength = 10
        self.__intelligence = 10
        self.__dexterity = 10

    # accessor methods
    def getStrength(self):
        return self.__strength

    def getIntelligence(self):
        return self.__intelligence

    def getDexterity(self):
        return self.__dexterity

    def getName(self):
        return self.__name

    # mutator methods
    def levelUp(self):
        self.__strength += 1
        self.__intelligence += 1
        self.__dexterity += 1

    def rename(self, name):
        try:
            name = name.split()
            for i in range(len(name)):
                name[i] = name[i].capitalize()
            self.__name = ' '.join(name)
        except AttributeError:
            print("Name not set! Incorrect data type")
tom = RPGCharacter("tom")
print("Before Level up:")
print(f"\tName: {tom.getName()}")
print(f"\tStrength: {tom.getStrength()}")
print(f"\tIntelligence: {tom.getIntelligence()}")
print(f"\tDexterity: {tom.getDexterity()}\n")

# level up!
tom.levelUp()
print("After level up:")
print(f"\tName: {tom.getName()}")
print(f"\tStrength: {tom.getStrength()}")
print(f"\tIntelligence: {tom.getIntelligence()}")
print(f"\tDexterity: {tom.getDexterity()}")
Before Level up
	Name: Tom
	Strength: 10
	Intelligence: 10
	Dexterity: 10

After level up!
	Name: Tom
	Strength: 11
	Intelligence: 11
	Dexterity: 11

Notice the extra code in the rename(self, name) method defined above. As you recall, the wonderful thing about private variables is the ability to check data before it is assigned and ensure that it is in the correct format before changing the attribute. You can also massage the data a little bit. This code makes sure that every word in the name being passed to us is capitalized before being set to the __name attribute. I added the for loop code to the constructor as well, which performs the same function. The try/except block in the method prevents the user from sending bad data, such as a float, to the attribute.

print(tom.getName())
tom.rename("tom bombadil")
print(tom.getName())
Tom
Tom Bombadil
# Let's try setting the name to a float
tom.rename(5323.34)
print(tom.getName())
Name not set! Incorrect data type
Tom Bombadil

Inheritance

Another powerful property of classes is the idea of inheritance. We can create a class that inherits the attributes and methods of another class. In our RPG, not all of our characters will be the same. For example, there will be mages, warriors, and bards, all with their own unique skills and attributes. Instead of having to start from scratch, we can simply inherit from the class we have already defined and extend the class with the new methods.

class Mage(RPGCharacter):
    def __init__(self, name):
        super(Mage, self).__init__(name)
        self._RPGCharacter__intelligence += 5

class Warrior(RPGCharacter):
    def __init__(self, name):
        super(Warrior, self).__init__(name)
        self._RPGCharacter__strength += 5

class Bard(RPGCharacter):
    def __init__(self, name):
        super(Bard, self).__init__(name)
        self._RPGCharacter__dexterity += 5

This is all we would need to begin the class of our new character. All of the accessor and mutator methods we have already defined are valid and present for these new classes. For each of our new character classes, we have given them a boost in a certain attribute. Since we are using private attributes, we make sure to put the name of the first class before the variable name as shown above to gain access to the variable.

gandalf = Mage("Gandalf")

print("Before Level up:")
print(f"\tName: {gandalf.getName()}")
print(f"\tStrength: {gandalf.getStrength()}")
print(f"\tIntelligence: {gandalf.getIntelligence()}")
print(f"\tDexterity: {gandalf.getDexterity()}\n")

gandalf.levelUp()
print("After level up:")
print(f"\tStrength: {gandalf.getStrength()}")
print(f"\tIntelligence: {gandalf.getIntelligence()}")
print(f"\tDexterity: {gandalf.getDexterity()}\n")
Before Level up
	Name: Gandalf
	Strength: 10
	Intelligence: 15
	Dexterity: 10

After level up!
	Strength: 11
	Intelligence: 16
	Dexterity: 11

Note: This is an extremely simple introduction to inheritance and I am glossing over a lot of issues with inheritance. If you are inheriting from a class in a library, there may be very good reason why an attribute is set to private and you may jeopardize the integrity of the code by modifying a private attribute like this. YMMV!

Hopefully, this was an easy introduction to the concept of objects and classes. It can be a daunting topic to a newcomer in programming, but with a little time and practice, they become second nature. The most important take away is that an object can be thought of as a physical object from the material world. In our example, we are representing a RPG character, which in turn represents a person in a fictional world with certain attributes and abilities. Good luck and happy coding!