Python Intro to OOP and Classes

Posted by

Intro

Just like other programming languages, Python also embraces object-oriented programming (OOP).   A class is the blueprint that describes what is available to that object.   A class contains methods and attributes.  Once a class is defined, you can create objects based off the class.  The object is also known as an instance of the class. That’s the power of OOP is that you can build multiple object instances that derive from a single class.  

Why use Classes?

Why would I want to do this?  The first reason is reusability.  Assume I have a class named dog that describes characteristics of a dog, I want to reuse that over and over because my program will contain multiple dogs.  Hence, I would create multiple object instances of the dog class.  That is very efficient to have one file used over and over.   The second reason is isolation.  In this example, I’m going to have unique values assigned to each dog.  For Example, I will likely have a dog breed attribute and color attribute that allows me to assign the dog a breed and color.  My program will contain dogs with different breeds and colors based on user input.  Each object instance created, is unique so the breed and color of the dog is tied to that object instance. 

For Example

class dog():
    def __init__(self):
        self.name: str
        self.breed: str
        self.color: str

	
# program starts here
# create first instance of dog
furry1 = dog()
furry1.name = "Pete"
furry1.breed = "German Shepard"
furry1.color = "red"


# create second instance of dog
furry2 = dog()
furry2.name = "Smokey"
furry2.breed = "Pug"
furry2.color = "black"
print(f'The names of both dogs are {furry1.name} and {furry2.name}')
print(f'The breeds of both dogs are {furry1.breed} and {furry2.breed}')

This outputs the following:

This displays the following output: 
The names of both dogs are Pete and Smokey.  The breeds of both dogs are German Shepard and Pug.

Methods can be used within a class.
For Example:

class dog():
    def __init__(self):
        self.name: str
        self.breed: str
        self.color: str
   
    def simpledesc(self):
        print()
        print(f"The Dog's name is {self.name} and breed is a {self.breed}")
        print(f"The Dog color is: {self.color}")
        print()


# create instance of dog
furry1 = dog()
furry1.name = "Pete"
furry1.breed = "German Shepard"
furry1.color = "red"

# create instance of dog
furry2 = dog()
furry2.name = "Smokey"
furry2.breed = "Pug"
furry2.color = "black"

# call the function
furry1.simpledesc()
furry2.simpledesc()

This outputs the following:

This outputs the same text as the previous image.

Lots to unpack with these above code samples.  First, the class keyword is used to define the class dog().  Everything indented underneath is part of that class.  The first function __init__, represents a constructor in python.  When the object is created (furry2 = dog()), the __init__ executes so everything within the body of this function runs.  In my simple example, I define three attributes that will be used later.  This function also requires the self-parameter.  Self represents the instance of the object and you’ll see why it becomes important in the next code sample.  For the second function, simpledesc(self) is an instance method.  You can spot these because they will always pass in a parameter of self which points to the instance of the dog class.  This enables the function to have full access to the attributes within the object.   If you already know what the values of the objects will be upon creation, you can just pass them in during object creation instead of the approach above where I assign each value on a separate line after object creation.  

Make Better use of a Constructor

In the next example, we will make better use of the constructor.  The code looks like:

class dog():
    def __init__(self, dogname: str, dogbreed: str, dogcolor: str):
        self.name = dogname
        self.breed = dogbreed
        self.color = dogcolor

    def simpledesc(self):
        print()
        print(f"The Dog's name is {self.name} and breed is a {self.breed}")
        print(f"The Dog color is: {self.color}")
        print()

# create instance of dog
furry1 = dog("Pete", "German Shepard", "red")

# create instance of dog
furry2 = dog("Smokey", "Pug", "black")

# call the function
furry1.simpledesc()
furry2.simpledesc()

This outputs the following:

this outputs the same text as the original image.

Import Classes

In the above examples, I’m running everything within a single python file.  It’s recommended for your classes to be in separate files to provide better organization and avoid confusion for future developers that need to review code you authored.  To utilize those classes in your main program, you’ll import them.  I’ll create a file for my class called thedogs.py and the main program will run in main.py.  The code now looks like:

File: thedogs.py

displays a code sample of thedogs.py file.

File: main.py

displays a code sample of the main.py file.

Static Methods

It’s possible to also used static methods within your class that are available to both the object instance as well as calling them outside of the class instance.  These are usually a function that provides generic functionality.  Two options are available to setup a static method inside a class.  The first is by creating a function without the self-parameter.  In fact, a static method can’t have the self-parameter assigned or it’s considered an instance method.  The second option is by using the static decorator.  This is the recommended approach and makes it clear to anyone reviewing the method within a class what type it is.  In the next example, I’ll go back to a single file that will include my program and class.

class dog():
    def __init__(self, dogname: str, dogbreed: str, dogcolor: str):
        self.name = dogname
        self.breed = dogbreed
        self.color = dogcolor

    def simpledesc(self):
        print()
        print(f"The Dog's name is {self.name} and breed is a {self.breed}")
        print(f"The Dog color is: {self.color}")
        print()

    @staticmethod
    def physicaltraits():
        res = ['Four Legs', 'Teeth', 'Coat of Hair']
        return res

# create instance of dog
furry1 = dog("Pete", "German Shepard", "red")

# call the instance method
furry1.simpledesc()

# call the static method directly
traits1 = dog.physicaltraits()

''' call the static method from the object instance '''
traits2 = furry1.physicaltraits()

''' print the results of both variables '''
print(traits1)
print()
print(traits2)

This outputs the following:

this displays command output.

The first call to the static method is calling it directly using the classname.methodname().  It assigns the return value to traits1.   The second call to the static method is calling from the object instance.  This also works but it’s important to note that the static method won’t have access to any of the class attributes or instance methods defined because it doesn’t have access to self.    

You have other features of OOP and classes like inheritance that I won’t cover for this intro but equally important. I may cover them in later posts however this should help give you the basics to get a good start.

Thank You,

Russ Maxwell