A Realistic example of Python Class

I would like to show you a realistic example of classes in action. I am going to build a set of classes that do something more concrete—recording and processing information about people. As you’ll see, what we call instances and classes in Python programming can often serve the same roles as records and programs in more traditional terms.

Specifically, in this chapter we’re going to code two classes:

  1. Person—a class that creates and processes information about people.
  2. Manager—a customization of Person that modifies inherited behavior.

Along the way, we’ll make instances of both classes and test out their functionality. When we’re done, I’ll show you a nice example use case for classes—we’ll store our instances in a shelve object-oriented database, to make them permanent. That way, you can use this code as a template for fleshing out a full-blown personal database written entirely in Python.

In the end, our classes will still be relatively small in terms of code, but they will demonstrate all of the main ideas in Python’s OOP model. Despite its syntax details, Python’s class system really is largely just a matter of searching for an attribute in a tree of objects, along with a special first argument for functions.

Step 1: Making Instances

OK, so much for the design phase—let’s move on to implementation. Our first task is to start coding the main class, Person. In your favorite text editor, open a new file for the code we’ll be writing. It’s a fairly strong convention in Python to begin module names with a lowercase letter and class names with an uppercase letter; like the name of self arguments in methods, this is not required by the language, but it’s so common that deviating might be confusing to people who later read your code. To conform, we’ll call our new module file person.py and our class within it Person, like this:

# file: python.py
class Person:   #start a class

All our work will be done in this file until later in this chapter. We can code any number of functions and classes in a single module file in Python, and this one’s person.py name might not make much sense if we add unrelated components to it later. For now, we’ll assume everything in it will be Person-related. It probably should be anyhow—as we’ve learned, modules tend to work best when they have a single, cohesive purpose.

Now, the first thing we want to do with our Person class is record basic information about people—to fill out record fields, if you will. Of course, these are known as instance object attributes in Python-speak, and they generally are created by assignment to self attributes in a class’s method functions. The normal way to give instance attributes their first values is to assign them to self in the __init__ constructor method, which contains code run automatically by Python each time an instance is created. Let’s add one to our class:

# file: python.py
class Person:   #start a class
    def __init__(self, name, job, pay):    # Constructor takes three arguments
        self.name = name    # Fill out fields when created
        self.job = job     # self is the new instance object
        self.pay = pay

This is a very common coding pattern: we pass in the data to be attached to an instance as arguments to the constructor method and assign them to self to retain them permanently. In Object Oriented terms, self is the newly created instance object, and name, job, and pay become state information—descriptive data saved on an object for later use. Although other techniques (such as enclosing scope reference closures) can save details, too, instance attributes make this very explicit and easy to understand.

Let’s make the job argument optional—it will default to None, meaning the person being created is not (currently) employed. If job defaults to None, we’ll probably want to default pay to 0, too, for consistency (unless some of the people you know manage to get paid without having jobs!):

# file: python.py
class Person:   #start a class
    def __init__(self, name, job=None, pay=0):    # Constructor takes three arguments
        self.name = name    # Fill out fields when created
        self.job = job     # self is the new instance object
        self.pay = pay

What this code means is that we’ll need to pass in a name when making Persons, but job and pay are now optional; they’ll default to None and 0 if omitted. The self argument, as usual, is filled in by Python automatically to refer to the instance object— assigning values to attributes of self attaches them to the new instance.

let’s test what we’ve got so far by making a few instances of our class and displaying their attributes as created by the constructor. We could do this interactively, but as you’ve also probably surmised by now, interactive testing has its limits—it gets tedious to have to reimport modules and retype test cases each time you start a new testing session. More commonly, Python programmers use the interactive prompt for simple one-off tests but do more substantial testing by writing code at the bottom of the file that contains the objects to be tested, like this:

# self test code
class Person:   #start a class
    def __init__(self, name, job=None, pay=0):    # Constructor takes three arguments
        self.name = name    # Fill out fields when created
        self.job = job     # self is the new instance object
        self.pay = pay
john = Person("John")
aman = Person("Aman", job='Programmer', pay=500000)
print(aman.name, aman.pay)
print(john.name, john.pay)

#Output-

Aman 500000
John 0

Notice here that the John object accepts the defaults for job and pay, but Aman provides values explicitly. Also note how we use keyword arguments when making aman; we could pass by position instead, but the keywords may help remind us later what the data is, and they allow us to pass the arguments in any left-to-right order we like. Again, despite its unusual name, __init__ is a normal function, supporting everything you already know about functions—including both defaults and pass-by-name keyword arguments.

You can also type this file’s test code at Python’s interactive prompt (assuming you import the Person class there first), but coding canned tests inside the module file like this makes it much easier to rerun them in the future.

We could split the test code off into a separate file, it’s often more convenient to code tests in the same file as the items to be tested. It would be better to arrange to run the test statements at the bottom only when the file is run for testing, not when the file is imported. That’s exactly what the module name check is designed for, as you learned in the preceding part of this book. Here’s what this addition looks like—add the require test and indent your self-test code:

# file: python.py
class Person:   #start a class
    def __init__(self, name, job=None, pay=0):    # Constructor takes three arguments
        self.name = name    # Fill out fields when created
        self.job = job     # self is the new instance object
        self.pay = pay
        
if __name__ == '__main__':
    # self test code
    john = Person("John")
    aman = Person("Aman", job='Programmer', pay=500000)
    print(aman.name, aman.pay)
    print(john.name, john.pay)

Step 2: Adding Behavior Methods

Everything looks good so far—at this point, our class is essentially a record factory; it creates and fills out fields of records (attributes of instances, in more Pythonic terms). Even as limited as it is, though, we can still run some operations on its objects. Although classes add an extra layer of structure, they ultimately do most of their work by embedding and processing basic core data types like lists and strings. In other words, if you already know how to use Python’s simple core types, you already know much of the Python class story; classes are really just a minor structural extension.

For example, the name field of our objects is a simple string, so we can extract last names from our objects by splitting on spaces and indexing. These are all core data type operations, which work whether their subjects are embedded in class instances or not:

name = "Aman Kharwal"    # Simple string, outside class
print(name.split())    # Extract last name
print(name.split()[-1])    # Or [1], if always just two parts

#Output-

[‘Aman’, ‘Kharwal’]
Kharwal

Similarly, we can give an object a pay raise by updating its pay field—that is, by changing its state information in place with an assignment. This task also involves basic operations that work on Python’s core objects, regardless of whether they are standalone or embedded in a class structure (I’m formatting the result in the following to mask the fact that different Pythons print a different number of decimal digits):

pay = 10000
pay *= 1.10
print('%.2f' % pay)

#Output- 11000.00

To apply these operations to the Person objects created by our script, simply do to john.name and aman.pay what we just did to name and pay. The operations are the same, but the subjects are attached as attributes to objects created from our class:

class Person:   #start a class
    def __init__(self, name, job=None, pay=0):    # Constructor takes three arguments
        self.name = name    # Fill out fields when created
        self.job = job     # self is the new instance object
        self.pay = pay
if __name__ == '__main__':
    # self test code
    john = Person("John Cena")
    aman = Person("Aman", job='Programmer', pay=500000)
    print(aman.name, aman.pay)
    print(john.name, john.pay)
    print(john.name.split()[-1])
    aman.pay *= 1.10
    print('%.2f' % aman.pay)

#Output-

Aman 500000
John Cena 0
Cena
550000.00

That’s it for today we will continue more in the next chapter.

Previous||Next Chapter