No. 14
Quite a few basic Python books and tutorials put off covering Python classes until so late in the book that some newcomers have gotten the idea that classes are somehow optional add-ons, and they put off learning them at all.
But they have gone at this wrong-way round. Almost every component of Python is an object.
Objects hold data and have methods to access and change that data
For example, strings, lists, tuples, sets and dictionaries are all objects, as are complex numbers. And they all have functions associated with them called methods that allow you to get and change that data.
list1 = [5, 20, 15, 6, 123] # create a list
x = list1.pop() # remove the last item, x=123
name = "Python lover"
newname = name.lower() # lower case "python lover"
myset = {1,4,7}
myset.update([1,3,5]) # {1, 3, 4, 5, 7}
This is how you manipulate some common Python objects. But how do you make your own objects?
Classes allow you to create new objects.
You create objects by first defining a class which describes that object. Rather than deal with cute Dog classes, let’s instead get right to work with a useful class describing an employee. Our employee class will contain the employees name, salary, benefits and an ID number.
class Employee():
def __init__(self, frname, lname, salary):
self.idnum: int #place holder
self.frname = frname #save name
self.lname = lname
self._salary = salary # and salary
self.benefits = 1000 # and benefits
@property
def salary(self): # get the salary
return self._salary
def setSalary(self, val): # store the salary
self._salary = val
Note that the values for each employee are set in the __init__ method, which gets called automatically when we create each instance of the Employee class, like this:
fred = Employee('Fred', 'Smythe', 1200)
)
sam = Employee('Sam', 'Snerd', 1300
The variables fred and sam contains an instance of the Employee class, with specific values for the name and salary and so forth. We can, of course create any number of such instances of the Employee class, one for each person we employ.
There can be many instances of a class, each holding different values.
Each instance can also be called an object.
The functions inside a class are called methods.
Collections of classes
So now, let’s consider where we are going to keep all those employee classes. You might keep them in a database, but within a program, some sort of collection seems like a good idea. We’ll define an Employees class that keeps the employees in a dictionary: each with its own id number as a key. It looks like this:
# Contains a dictionary of Employees, keyed by ID number
class Employees:
def __init__(self):
self.empDict = {} # create a dictionary for employees
self.index=101 # starting ID number
def addEmployee(self, emp):
emp.idnum = self.index # set its ID
self.index += 1 # go on to next ID
self.empDict.update({emp.idnum: emp}) # add to dictionary
# find the employee with that ID number
def find(self, idnum):
return self.empDict.get(idnum)
In the above Employees class we create an empty dictionary and a starting ID number. Every time we add an employee to the class, it increments that index value.
We create the classes in some simple outer class called HR:
# This creates a small group of Employees
class HR():
def __init__(self):
self.empdata = Employees()
self.empdata.addEmployee(Employee('Sarah', 'Smythe', 2000))
self.empdata.addEmployee(Employee('Billy', 'Bob', 1000))
self.empdata.addEmployee(Employee('Edward', 'Elgar', 2200))
def listEmployees(self):
dict = self.empdata.empDict
for key in dict:
empl= dict[key] # get the employee instance
# and print it out
print (empl.frname, empl.lname, empl.salary)
You can keep class instances inside other classes
More kinds of employees
But in a real company, we might well have other kinds of employees. We might have temporary workers, who get paid (the same or less) but do not get benefits. Rather than creating a completely new class, we can derive a new TempEmployee class from the Employee class. It has all the same methods, so you don’t have to write that code over again. You just have to write the parts that are now.
# Temp employees get no benefits
class TempEmployee(Employee):
def __init__(self, frname, lname, salary):
super().__init__(frname, lname, salary)
self.benefits = 0
And further, we can create another class from that called Intern. Interns do not get benefits, and have a low salary cap. So, we write a new derived class that checks the salary to make sure it isn’t above the cap. (Of course we don’t really think this is a good idea economically.)
class Intern(TempEmployee):
def __init__(self, frname, lname, sal):
super().__init__(frname, lname, sal)
self.setSalary(sal) # cap salary
# limit intern salary
def setSalary(self, val):
if val > 500:
self._salary = 500
else:
self._salary = val
Now, we can create a set of employees in each of these classes:
# This creates a small group of Employees
class HR():
def __init__(self):
self.empdata = Employees()
self.empdata.addEmployee(Employee('Sarah', 'Smythe',2000))
self.empdata.addEmployee(TempEmployee('Billy', 'Bob', 1000))
self.empdata.addEmployee((Intern('Arnold', 'Stang', 800)))
def listEmployees(self):
dict = self.empdata.empDict
for key in dict:
empl= dict[key]
print (empl.frname, empl.lname, empl.salary)
And note that while two of them are derived classes, they are still Employee objects and you can list them out in the same way. Here is the output.
Sarah Smythe 2000
Billy Bob 1000
Arnold Stang 500
Derived classes let you create related classes with different properties or computations.
Creating new classes for different application frameworks
As we explained in our discussion the Façade pattern`` last week, we can create four small classes to describe database connections and computations. For example, our Database class connects to a MYSQL database like this:
class Database():
def __init__(self, *args):
#self._db = MySQLdb.connect(args[0], args[1], args[2])
self.host=args[0]
self.userid=args[1]
self.pwd = args[2]
self._cursor = self._db.cursor()
def commit(self):
self._db.commit()
def create(self, dbname):
self.cursor.execute("drop database if exists "+dbname)
self._cursor.execute("Create database "+ dbname)
self._dbname = dbname
self._db=MySQLdb.connect(
self.host, self.userid, self.pwd, dbname)
self.cursor.execute("use "+dbname)
self._cursor= self._db.cursor()
def getTables(self):
self._cursor.execute("show tables")
# create array of table objects
self.tables = []
rows = self._cursor.fetchall()
for r in rows:
self.tables.append(Table(self._cursor, r))
return self.tables
However, when we connect to a SQLITE database, the __init__ method differs. But since the rest of the methods in the database class are unchanged, we can simply derive the database connect class from the original Database class. Note that the query to get the table list also changes.
class SqltDatabase(Database):
def __init__(self, *args):
self._db = sqlite3.connect(args[0])
self._dbname = args[0]
self._cursor = self._db.cursor()
def create(self, dbname):
pass
def getTables(self):
self._cursor.execute(
"select name from sqlite_master where type='table'")
# create array of table objects
self.tables = []
rows = self._cursor.fetchall()
for r in rows:
self.tables.append(SqltTable(self._db, r))
return self.tables
And the rest in unchanged!
Classes can create new function when you create interfaces to new libraries
Deriving your own class from existing classes
It is not unusual to derive new class from standard Python classes. For example, if we write a simple program using the tkinter library to display two buttons, the display looks like this.
And all the program does is pop up one of two messagebox windows.
The basic code to build that window is simply:
def build(self):
root = Tk()
root.geometry("200x100")
root.title("Basic buttons")
helloButton = Button(root, text="Hello", width=8)
helloButton.pack(side=LEFT, padx=20)
leaveButton = Button(root, text="Leave", width=8)
leaveButton.pack(side=RIGHT, padx=20)
mainloop()
But, how do we intercept the button clicks?
In the simplest case, we add a command= modifier to each button creation, telling the button where the method is that it should call.
The problem with that basic approach is that we don’t know where that method should be located? Should it be right in the Builder class, or does that clutter it up with things that really should be elsewhere? Perhaps in “one grunch and an eggplant over there”?
A better possibility is to create a little ButtonFuncs class and have the buttons call its methods:
class ButtonFuncs():
def __init__(self):
pass
def hello(self):
messagebox.showinfo("Hi", "Hello")
def exit(self):
ans = messagebox.askquestion("Leaving so soon?",
"Do you really want to exit")
if ans == 'yes':
sys.exit()
Then the creation of the buttons include those command statements like this:
bf = ButtonFuncs()
helloButton = Button(root, text="Hello", width=8, command = bf.hello) helloButton.pack(side=LEFT, padx=20)
leaveButton = Button(root, text="Leave", width=8, command = bf.exit) leaveButton.pack(side=RIGHT, padx=20)
And this is not a bad design: and not unlike the sort of Mediator class we have developed in other articles.
Deriving a new button
But a better way is to put the command right inside the Button code. These two buttons are derived from the Button class, but call a command right inside the button class:
class HelloButton(Button):
def __init__(self, root, **kwargs):
super().__init__(root, text="Hello",
command = self.comd, **kwargs)
def comd(self):
messagebox.showinfo("Hi", "Hello")
class LeaveButton(Button):
def __init__(self, root, **kwargs):
super().__init__(root, text="Leave",
command = self.comd, **kwargs)
def comd(self):
ans = messagebox.askquestion("Leaving so soon?",
"Do you really want to exit")
if ans == 'yes':
sys.exit()
Then there is no command= statement in the Builder class, because it is enclosed in each of the two buttons.
Derived classes can add function to existing classes.
Summary
So, in summary, we use classes to create more structured and flexible programs.
Classes allow you to create new objects.
There can be many instances of a class, each holding different values.
Each instance can also be called an object.
The functions inside a class are called methods.
You can keep class instances inside other classes
Derived classes let you create related classes with different properties or computations.
Classes can create new function when you create interfaces to new libraries
Derived classes can add function to existing library classes.
So, that is why we use classes every day!
All code can be downloaded from GitHub under jameswcooper/newsletter.
Subscribe to this newsletter so you don’t miss a single Monday’s issue!