Classes: definition and constructor

Although nothing we’ve done so far might suggest it, Python is actually a fully object-oriented language. Pretty well everything in a Python program is implemented as an object. From primitive data types like ints and floats, right up to compound data types like lists and tuples, and even functions themselves, are all objects. An object, technically speaking, is an instance of a data type called a class.

As a programmer, you can define your own classes and create objects from them. Although most books on object oriented programming (OOP, for short) treat classes as an advanced topic, I like to think of them as fundamental to any program of more than a few lines in length.

I find it helpful to think of classes and objects as nouns, in the sense that they describe ‘objects’ (in the ordinary English sense of the word). Functions are like verbs, as they describe actions. Just as in English, a verb is useless without something on which it can act, and that ‘something’ is a noun. To build a clearly structured program, it’s better to start by thinking of the nouns around which the program will run. Only after we’ve built the nouns (classes and objects) do we start to think about the actions (functions) that use them.

Creating a Visual Studio project with classes

Although it’s possible to experiment with classes in the Python console, it soon becomes tedious and frustrating, since the definition of a class usually takes several lines with correct indenting. It’s also possible to write all your OOP code in a single file, but again, for a program that uses several classes, this rapidly becomes ungainly.

It’s a good idea to get into the habit of writing each class in a separate file within your project. Fortunately, this is easy to do in Visual Studio. Here’s an example.

We are going to create a project called Company which will describe some attributes of a company, including lists of employees, product lines and so on. First, create the project as described earlier, and name the project Company.

Next, we will add a class named Employee to the project. The Employee class is used to store some personal data on a single employee of the company. To do this, bring up the Solution Explorer (from Visual Studio’s View menu). Right-click on the project name (Company) and select Add > New item… In the dialog box that appears, select Python class, and enter the name Employee in the Name box at the bottom, then click on the Add button at the lower right. You should see the file Employee.py appear in the list of files in Solution Explorer. Open this file for editing, and we’re set to build our first class.

Class definition

You’ll see that Visual Studio provides a couple of lines for the new class:

class Employee(object):
    """description of class"""

The first line shows the basic class syntax. The keyword class specifies that what follows is a class definition. Next we give the name of the class (Employee). Visual Studio includes (object) after the class name. This is actually optional (you can delete it if you like; the following code will still work). [For those who want to jump ahead a bit, the (object) indicates that the Employee class inherits the more fundamental object class, but more on that later. In Python, every class inherits the object class anyway, which is why including it explicitly here is optional.] Finally, the class definition line must end with a colon.

The second line is a comment that describes what the class represents. This is optional, but it is good practice to include it, so you should edit the comment to say something about the class.

Class members

There are two main types of things we can add to a class: data and functions. The easiest way to see how this is done is through a simple example.

class Employee(object):
    """Employee data"""

    def __init__(self, first, last, birth, country = 'Canada'):
        self.firstName = first
        self.lastName = last
        self.birthYear = birth
        self.country = country
        
    def printEmployee(self):
        print(self.firstName, self.lastName, self.birthYear, self.country)

    def testFunc(self):
        print('Calling test function.')

The Employee class contains 4 bits of data about each employee: first name, last name, year of birth and country of origin. Here these data fields are defined in a constructor function.

__init__()

Python provides a special function called __init__() to serve as the constructor. (The name __init__ has two underscores before and two more underscores after the ‘init’.) The constructor serves to initialize the data fields (and perform any other tasks needed to create an object).

The __init__() function behaves just like any other Python function in most respects, but there are a couple of things that are different. First, note that the first argument is given as self. In fact, self must be the first argument in all functions that are to be called by an instance of the class (an object). Programmers from other OOP languages such as C++, C# and Java will know self as the keyword this from these languages. [In fact, it’s possible to call self anything you like, so you can use this instead of self if you insist, but it’s not recommended since it will just cause confusion.] What does this represent?

Recall that the main purpose of a class is that it defines a data type from which we can create some objects. In a sense, a class is like a recipe for a cake. The recipe defines how to make the cake, and using that recipe, we can bake as many cakes as we like. Each cake is an instance of the recipe. When we use a class to create an object, each object is an independent entity within the program. Thus if we want to call one of the class’s functions from a particular object, that function has to know which object is calling it.

The self argument in a class’s function contains a reference to the particular object that is calling the function. As an example, here’s how we would create an instance of Employee and initialize the data fields. In your Visual Studio project, go to the main file (named Company.py if you called your project Company) and enter the code:

from Employee import *

glenn = Employee('Glenn', 'Rowe', 1953)

The first line tells the Company.py file to import the defintions contained in the Employee file. We then create a new Employee object called glenn by giving the class name Employee followed by the arguments in parentheses. Compare these arguments with those in the __init__() function above.

The first thing you should notice is that there are 3 arguments passed to the constructor, yet there are 5 arguments in the definition of __init__(). As the last argument (country) in __init__() has a default value, we don’t need to provide a value for it. However, the other 4 arguments don’t have default values, so it would seem that we’re missing an argument when we create the Employee object.

However, the self argument is implicitly passed to a class’s function without the need for it to be included in the argument list when the function is called. Essentially what is happening is that the object glenn is passed into __init__() as the self argument, followed by the 3 arguments specifying the first and last names and the year of birth.

You’ll also note that __init__() has no return statement, yet we assign glenn to the result of calling it. Again, this is handled implicitly so you don’t have to worry about it.

In our example above, calling __init__() defines the 4 data fields that every instance of Employee will possess. We do not need to define these fields anywhere else in the class (unlike other OOP languages). Also, in line with Python’s dynamic typing, we don’t need to specify data types for the fields, and any of these fields can be assigned to any data type.

Class functions

A class can contain any number of functions. If a function is to be called from an object, the first argument of that function must be self, even if the function makes no reference to any of the data fields of the object. In the Employee example above, testFunc(self) does nothing apart from print out a fixed message, but if you plan on calling it from an instance of Employee, it must have the self argument.

It is possible to include functions in a class that don’t have self as their first argument, but these functions must be called directly from the class name rather than from any object created from the class. They are analogous to static methods in other languages.

Apart from these considerations, class functions work pretty much the same as functions that aren’t associated with any class. They can have arguments with default values, keyword arguments and so on.

Here’s an example illustrating what we’ve discussed so far. In the file Employee.py we have the class definition:

class Employee(object):
    """Employee data"""
    music = 'Beethoven'

    def __init__(self, first, last, birth, country = 'Canada'):
        self.firstName = first
        self.lastName = last
        self.birthYear = birth
        self.country = country
        
    def printEmployee(self):
        print(self.firstName, self.lastName, self.birthYear, self.country, self.music)

    def setMusic(self, myMusic):
        self.music = myMusic

    def testFunc(self):
        print('Calling test function.')

    def staticFunc():
        print('Static function called.')
        print('Music:', Employee.music)

This example illustrates a possible source of confusion. On line 3, we’ve added a data field music. Notice that there is no reference to self, and it is initialized outside of any function. Such a data field belongs to the class as a whole, and not to any specific object. In other languages, this is often called a ‘static’ variable. However…. if an object has no corresponding data field with the name as the class variable, we can still refer to an object variable with that name, and we will get the value of the class variable as a result. Consider the following code in file Company.py:

from Employee import *

glenn = Employee('Glenn', 'Rowe', 1953)
glenn.printEmployee()
glenn.testFunc()
glenn.setMusic('Schubert')
glenn.printEmployee()
Employee.staticFunc()

We initialize the glenn Employee and then call printEmployee(). This shows a value of ‘Beethoven’ for self.music, even though the glenn object was never assigned a self music data field.

Now look at the function setMusic() on line 14 of Employee. Here we assign a value to self.music. This means that the object that called setMusic() now has its own local variable called music, and this overrides the class variable. However, the class variable has not changed.

We’ve also added a function staticFunc() on line 20 of Employee. This function does not have a self argument and thus cannot be called from an object; it must be called from the Employee class itself.

Employee.staticFunc() shows how to call a class function that does not have a self argument: we give the class name (not the name of an object created from that class) followed by the function name. The code on line 20 of Employee shows that if we wish to refer to the class variable music, we must prefix it with the name of the class and not self.

Exercise

Add a class called Product to the Company program above. The Product class should contain a collection of data describing one particular product produced by the company. The class should satisfy the requirements:

  • The constructor should require an argument giving the name of the product.
  • The constructor should then accept an arbitrary list of keyword arguments, where each argument gives the name of a property and its associated value.
  • The class should contain a class variable that keeps track of how many Product objects have been created.
  • The class should have a function printProps() that prints out the name and list of properties for one product.
  • The class should have a function that prints out the total number of objects created.

Write appropriate code in the main Company.py file to create several Products and print out their properties and the total number of Products created.

See answer

In the file Product.py:

class Product(object):
    """a product sold by the company"""

    numProducts = 0

    def __init__(self, name, **kwargs):
        self.name = name
        self.properties = kwargs
        Product.numProducts += 1

    def printProps(self):
        print('Name:', self.name)
        keys = self.properties.keys()
        for k in keys:
            print(k + ':', self.properties[k])
        print()

    def printNumProducts():
        print('Total number:', Product.numProducts)

We define the class variable numProducts on line 4. It is incremented each time the constructor is called, on line 9.

The constructor uses an arbitrary collection of keyword arguments to assign the properties. Recall that this collection is implemented as a dictionary, so self.properties is a dictionary of keys (property names) and their values. However, the name field is required, so occurs before **kwargs.

One possible program to test this class is in Company.py:

from Product import *

name = ['widget', 'frobule', 'thingy']
items = [
    Product(name[0], legs = 4, key = True, windup = True, unitCost = 10.50),
    Product(name[1], fuel = 'hydrogen', lifespan = 10, key = False),
    Product(name[2], legs = 10, unitCost = 0.95, warranty = True)
    ]
for i in items:
    i.printProps()

Product.printNumProducts()

We’ve created a list of 3 product names, and then created the items list of Products for each of these names. Note that we can specify the property names to be anything we like, since we’re storing the properties as a dictionary in the Product objects.

The last line calls the Product class function to print out the total number of Products. The output looks like this:

Name: widget
legs: 4
key: True
windup: True
unitCost: 10.5

Name: frobule
fuel: hydrogen
lifespan: 10
key: False

Name: thingy
legs: 10
unitCost: 0.95
warranty: True

Total number: 3

 

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.