Libraries and Modules in Python

Documenting Classes

Overview:

  • Teaching: 10 min
  • Exercises: 5 min

Questions

  • What about documenting my classes?
  • What do I need to document?

Objectives

  • Know how to document classes
  • Understand the difference between private and public variables and functions

Adding Docstrings to a class

It is almost as easy to document a class as it is to document a function. Simply add docstrings to all of the classes functions, and also below the class name itself. For example, here is a simple documented class

In [1]:
class Demo:
    """This class demonstrates how to document a class.
    
       This class is just a demonstration, and does nothing.
       
       However the principles of documentation are still valid!
    """
    
    def __init__(self, name):
        """You should document the constructor, saying what it expects to 
           create a valid class. In this case
           
           name -- the name of an object of this class
        """
        self._name = name
    
    def getName(self):
        """You should then document all of the member functions, just as
           you do for normal functions. In this case, returns
           the name of the object
        """
        return self._name
In [2]:
d = Demo("cat")
In [3]:
help(d)
Help on Demo in module __main__ object:

class Demo(builtins.object)
 |  Demo(name)
 |  
 |  This class demonstrates how to document a class.
 |  
 |  This class is just a demonstration, and does nothing.
 |  
 |  However the principles of documentation are still valid!
 |  
 |  Methods defined here:
 |  
 |  __init__(self, name)
 |      You should document the constructor, saying what it expects to 
 |      create a valid class. In this case
 |      
 |      name -- the name of an object of this class
 |  
 |  getName(self)
 |      You should then document all of the member functions, just as
 |      you do for normal functions. In this case, returns
 |      the name of the object
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

Hidden (private) Functionality

Often, when you write a class, you want to hide member data or member functions so that they are only visible within an object of the class. For example, above, the self._name member data should be hidden, as it should only be used by the object.

You control the visibility of member functions or member data using an underscore. If the member function or member data name starts with an underscore, then it is hidden. Otherwise, the member data or function is visible.

For example, we can hide the getName function by renaming it to _getName

In [4]:
class Demo:
    """This class demonstrates how to document a class.
    
       This class is just a demonstration, and does nothing.
       
       However the principles of documentation are still valid!
    """
    
    def __init__(self, name):
        """You should document the constructor, saying what it expects to 
           create a valid class. In this case
           
           name -- the name of an object of this class
        """
        self._name = name
    
    def _getName(self):
        """You should then document all of the member functions, just as
           you do for normal functions. In this case, returns
           the name of the object
        """
        return self._name
In [5]:
d = Demo("cat")
In [6]:
help(d)
Help on Demo in module __main__ object:

class Demo(builtins.object)
 |  Demo(name)
 |  
 |  This class demonstrates how to document a class.
 |  
 |  This class is just a demonstration, and does nothing.
 |  
 |  However the principles of documentation are still valid!
 |  
 |  Methods defined here:
 |  
 |  __init__(self, name)
 |      You should document the constructor, saying what it expects to 
 |      create a valid class. In this case
 |      
 |      name -- the name of an object of this class
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

Member functions or data that are hidden are called "private". Member functions or data that are visible are called "public". You should document all public member functions of a class, as these are visible and designed to be used by other people. It is helpful, although not required, to document all of the private member functions of a class, as these will only really be called by you. However, in years to come, you will thank yourself if you still documented them... ;-)

While it is possible to make member data public, it is not advised. It is much better to get and set values of member data using public member functions. This makes it easier for you to add checks to ensure that the data is consistent and being used in the right way. For example, compare these two classes that represent a person, and hold their height.

In [7]:
class Person1:
    """Class that holds a person's height"""
    def __init__(self):
        """Construct a person who has zero height"""
        self.height = 0
In [8]:
class Person2:
    """Class that holds a person's height"""
    def __init__(self):
        """Construct a person who has zero height"""
        self._height = 0
    
    def setHeight(self, height):
        """Set the person's height to 'height', returning whether or 
           not the height was set successfully
        """
        if height < 0 or height > 300:
            print("This is an invalid height! %s" % height)
            return False
        else:
            self._height = height
            return True
        
    def getHeight(self):
        """Return the person's height"""
        return self._height

The first example is quicker to write, but it does little to protect itself against a user who attempts to use the class badly.

In [9]:
p = Person1()
In [10]:
p.height = -50
In [11]:
p.height
Out[11]:
-50
In [12]:
p.height = "cat"
In [13]:
p.height
Out[13]:
'cat'

The second example takes more lines of code, but these lines are valuable as they check that the user is using the class correctly. These checks, when combined with good documentation, ensure that your classes can be safely used by others, and that incorrect use will not create difficult-to-find bugs.

In [14]:
p = Person2()
In [15]:
p.setHeight(-50)
This is an invalid height! -50
Out[15]:
False
In [16]:
p.getHeight()
Out[16]:
0
In [17]:
p.setHeight("cat")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-17-578768c23ed2> in <module>
----> 1 p.setHeight("cat")

<ipython-input-8-0dc7e34b7d84> in setHeight(self, height)
      9            not the height was set successfully
     10         """
---> 11         if height < 0 or height > 300:
     12             print("This is an invalid height! %s" % height)
     13             return False

TypeError: '<' not supported between instances of 'str' and 'int'
In [18]:
p.getHeight()
Out[18]:
0

Exercises

1

Below is the completed GuessGame class from the previous lesson. Add documentation to this class.

In [19]:
class GuessGame:
    def __init__(self, secret, max_guesses=5):
        self._secret = secret
        self._nguesses = 0
        self._max_guesses = max_guesses
    def guess(self, value):
        if (self.nGuesses() >= self.maxGuesses()):
            print("Sorry, you have run out of guesses")
        elif (value == self._secret):
            print("Well done - you have guessed my secret")
        else:
            self._nguesses += 1
            print("Try again...")
    def nGuesses(self):
        return self._nguesses
    def maxGuesses(self):
        return self._max_guesses

2

Below is a class that uses public member data to store the name and age of a Person. Edit this class so that the member data is made private. Add get and set functions that allow you to safely get and set the name and age.

In [22]:
class Person:
    def __init__(self, name="unknown", age=0):
        self.setName(name)
        self.setAge(age)

3

Add a private member function called _splitName to your Person class that breaks the name into a surname and first name. Add new functions called getFirstName and getSurname that use this function to return the first name and surname of the person.

Solution

Key Points:

  • You document classes in much the same was as functions
  • You can 'hide' functions and variables from your users by making them private e.g. _variable or _function