Learning Python: Classes and Methods
By: Michael Mcmillan
Tags: Python Programming, Python Object, Learning Python, Object Oriented, Learn To Program

Photo by Chris Ried on Unsplash

In my last article, I introduced Python classes by demonstrating how to create class objects. I showed how to add attributes to an object once the object is instantiated, but I didn’t demonstrate how to embed functions (called methods when defined in a class) in the class definition. In this article, I’ll demonstrate how to define and use methods with class objects.

Before I describe how to create Python class methods, let me spend a minute talking about why class methods are important. The methods of a class make up the class’s interface, which is how programs interact with the class. By defining a set of methods for a class, you are determining how a class is used, since the only way to get an object to do anything is via the methods you define.

You should define a class interface carefully, only including those methods that are necessary to the proper use of the class. Class methods can also serve as gateways to a class’s attributes. By defining a method for accessing a class attribute, you can control how outside code can affect the attribute.

The example I have used before is a person’s age. Most people do not live much beyond 85 years of age or so. However, a variable storing a Python number can hold a value much larger and much smaller than that (think negative value). A good class interface design will include methods that check to make sure valid data is being entered for a class attribute.

Defining a Class Method

First, we need to define a class. Let’s work with date objects. Here is the start of a Date object definition:

class Date:

"""Date object""" d1 = Date()

d1.month = 11

d1.day = 16

d1.year = 2020

If we want to print the date, we can write a function that takes a Date object and displays the date stored in the object:

def printDate(dt):

print("%2d/%2d/%4d" % (dt.month, dt.day, dt.year))

If we put a call to this function at the end of the program above:

printDate(d1)

The output is:

11/16/2020

The problem with defining functions like this is we are violating one of the principles of object-oriented programming — encapsulation. Encapsulation refers to the bundling of data along with the functions (methods) that operate on that data.

When we encapsulate data and methods we are helping to ensure that the methods called from an object will work with the object’s data in the proper manner. We can also use class methods to make sure the data we assign to an object is legal and valid.

An example will help here. Suppose we have a Person object. A person has an age. Most people live to their 70s or 80s these days, at least, but not many people live past 100. However, when an age is assigned to a Person object, it can be any legal Python number. This means we can assign a huge number as an age, such 1,582,483, or we can assign a very small number as an age, such as -212.

When we define a method for assigning the age to a Person, however, we can control what values are assigned to the age attribute. I’ll demonstrate how that works with Date objects soon.

Now back to printing a date by creating a class method. To create a class method, just put the definition inside the class definition, like this:

class Date: def printDate(self):

print("%2d/%2d/%4d" % self.month, self.day, self.year))

Now we can call the method directly from a Date object using the dot operator. Here is a program that demonstrates how to do this (I’ve included the class definition again):

class Date: def printDate():

print("%2d/%2d/%4d" % (self.month, self.day, self.year)) tomorrow = Date()

tomorrow.month = 11

tomorrow.day = 17

tomorrow.year = 2020

tomorrow.printDate()

The output from this program is:

11/17/2020

Clearly, I need to explain what the keyword self is doing in this definition. The class definition needs a way to refer to the current object that the code is working on. You might even say it is the current object that has the focus of the program. The self keyword provides this reference.

As you can see, self becomes the parameter whenever we write a method that is using the data of the current object since we need a way to retrieve the data using the dot operator.

Other Class Methods

A method to print the values of an object’s attributes is important but there are many other methods a class can need. A Date class, for example, might want a method that lets you increment the date by one day or some other unit.

Here is a new Date class definition that includes an increment method, along with a program that tests the method:

class Date: def printDate(self):

print("%2d/%2d/%4d" % (self.month, self.day, self.year))



def increment(self, unit):

"""increment the date by a day or more"""

self.day = self.day + unit d1 = Date()

d1.month = 11

d1.day = 16

d1.year = 2020

d1.printDate()

d1.increment(1)

d1.printDate()

d1.increment(3)

d1.printDate()

The output from this program is:

11/16/2020

11/17/2020

11/20/2020

All object-oriented programming languages include a special method that is used to initialize a new object when it is instantiated. This method is called a constructor. In most languages, the constructor method has the same name as the class, but in Python the constructor method is named: __init__ .

The __init__ method is used to initialize the attributes of a class. The constructors of many programming languages allow you to write the definitions for multiple constructors so that the attributes can be initialized in different ways. For example, a Date object might be initialized with the month, the day, and the year, but it could also be initialized with just the month and the year, or even just the month and the day. A Date can also be initialized with no data and the default values of the arguments are assigned to the attributes.

In Python, though, you instead define the __init__ method with default arguments. Here is the __init__ method definition for our Date class:

class Date:



def __init__(self, month=0,day=0,year=0):

self.month = month

self.day = day

self.year = year # rest of Date definition

Here are some examples of instantiating different Date objects with our new __init__ method:

d1 = Date(11,17,2020)

d1.printDate()

d2 = Date(11,17)

d2.printDate()

d3 = Date()

d3.printDate()

The output from this code is:

11/17/2020

11/17/2020

0/ 0/2020

Using __str__ to Display Object State

In object-oriented programming, referring to an object’s state is to refer to the values stored in all the object’s attributes at any given time. There are many times when you want to see an object’s state, especially during debugging. Python provides a special object method for displaying state: the __str__ method. For those of you familiar with Java, __str__ is similar to the to_strin g Java method.

Here is how a _str__ method could look for the Date class:

def __str__(self):

return "%2d/%2d/%4d" % (self.month, self.day, self.year)

The __str__ definition is similar to the printDate method, but instead of calling the print function it returns the current date in the same format used with the printDat e method.

One of the nice features of __str__ is that you can call it by simply referencing the object rather than having to call the method directly. Here’s what I mean:

d1 = Date(11,17,2020)

print(d1)

which outputs:

11/17/2020

As I mentioned earlier, most uses of the __str__ method are for debugging. You should write a separate method for displaying an object’s attributes that is callable.

Overloading Operators in Python

When you redefine how an operator works it is called operator overloading. Python allows you to overload many of its operators. A full list of operators you can overload is found here. The documentation refers to it as emulation rather than overloading.

Operator overloading is important because its better to use the standard Python operators when working with your class objects than to have to call methods. For example, let’s say I am working with a class that counts things and I have two objects I want to add together. Which of the following two lines looks easier to use?

totalCounters = counter1 + counter2

totalCounters = counter1.add(counter2)

The first line is more intuitive.

Let’s see how to overload the + operator by implementing the Counter class I just described. Here’s the code for the class along with a simple test program:

class Counter:

def __init__(self):

self.count = 0 def increment(self):

self.count = self.count + 1 def __str__(self):

return str(self.count) laps = Counter()

for i in range(1,6):

laps.increment()

print("Laps run by laps:",laps)

moreLaps = Counter()

for i in range(1,4):

moreLaps.increment()

print("Laps run by moreLaps:",moreLaps)

The output from this program is:

Laps run: 5

Laps run by moreLaps: 3

Now let’s see how to overload the + operator. To do this, we write a new definition for __add__ inside the Counter class definition that tells Python what to do when two Counter objects are combined with the + operator. Here is the code for __add__ that is placed inside the class definition:

def __add__(self, cntr):

return self.count + cntr.count

The self parameter is for the Counter object to the left of the operator and the cntr parameter is for the Counter object to the right of the operator. The count attributes are called for both parameters, added together, and that result is returned.

Here is a test program and the output:

laps = Counter()

for i in range(1,6):

laps.increment()

print("Laps run by laps:",laps)

moreLaps = Counter()

for i in range(1,4):

moreLaps.increment()

print("Laps run by moreLaps:",moreLaps)

totalLaps = laps + moreLaps

print("Total laps run:", totalLaps) Laps run by laps: 5

Laps run by moreLaps: 3

Total laps run: 8

Let’s overload one more operator: the == operator. The code for overloading this operator involves using the == operator to test the value stored in the count attribute of two objects. Here’s the code places inside the definition of the Counter class:

def __eq__(self, cntr):

return self.count == cntr.count

Here is a program for testing our overloaded == operator:

laps = Counter()

for i in range(1,6):

laps.increment()

print("Laps run by laps:",laps)

moreLaps = Counter()

for i in range(1,6):

moreLaps.increment()

print("Laps run by moreLaps:",moreLaps)

if (laps == moreLaps):

print("Laps and moreLaps have the same number of laps.")

else:

moreLaps.increment()

print("Laps run by laps:",laps)

print("Laps run by moreLaps:",moreLaps)

if (laps == moreLaps):

print("Laps and moreLaps have the same number of laps.")

else:

print("Laps and moreLaps have a different number of laps.")

Here is the output:

Laps run by laps: 5

Laps run by moreLaps: 5

Laps and moreLaps have the same number of laps.

Laps run by laps: 5

Laps run by moreLaps: 6

Laps and moreLaps have a different number of laps.

That wraps up my discussion of creating classes in Python. In my next article I’ll discuss how to perform inheritance and polymorphism in Python, which greatly increase the power and usefulness of object-oriented programming.

Thanks for reading and please email me at mmmcmillan1@att.net with your comments and suggestions or leave a response below.