Libraries and Modules in Python

Modules

Overview:

  • Teaching: 10 min
  • Exercises: 5 min

Questions

  • How can I re-use my code in other scripts?
  • How can I make it easier for others to use my code?

Objectives

  • Understand how to create a module for use in other code, scripts or workflows.

You can turn any Python script that you write into a module that other people can import and use in their own code.

For example;

In [1]:
import superhero
Is it a bird? Is it a plane? No, it's Superman!!!
Superman will battle Lex Luther. The winner is Superman
Lex steals some krytonite...
They battle again... The winner is Lex Luther

What has happened here???

There is a file in code/superheros called superhero.py. The line import superhero will look in the current directory as well as at the path of any installed modules, to find a file called superhero.py. It then runs this file, just as if you had typed it into the screen.

This is just a simple Python script, which we can print out using

In [2]:
import inspect
lines = inspect.getsource(superhero)
print(lines)
"""
This module provides a set of classes for creating superheros
and supervillains. Have fun!

Author - Christopher Woods
License - BSD
"""

class Superhero:
    """This class allows you to create your own Superhero"""
    def __init__(self, name, weakness):
        """Construct a superhero with the specified name and the 
           specified weakness
        """
        self.setName(name)
        self.setWeakness(weakness)

    def setName(self, name):
        """Set the name of the superhero"""
        self._name = name

    def setWeakness(self, weakness):
        """Set the weakness of the superhero"""
        self._weakness = weakness

    def getName(self):
        """Return the name of the superhero"""
        return self._name

    def getWeakness(self):
        """Return the weakness of the superhero"""
        return self._weakness

    def isVulnerableTo(self, item):
        """Return whether or not this superhero is 
           vulnerable to 'item'"""
        return self.getWeakness().lower() == item.lower()

class Supervillain:
    """This class allows you to create your own supervillain"""
    def __init__(self, name):
        self.setName(name)
        self._loot = []

    def setName(self, name):
        """Set the name of the villain"""
        self._name = name

    def getName(self):
        """Return the name of the villain"""
        return self._name

    def steal(self, item):
        """Tell the villain to steal 'item'"""
        self._loot.append(item)

    def getLoot(self):
        """Return all of the loot that this villain has stolen"""
        return self._loot

def battle(superhero, supervillain):
    """This function will pitch the superhero and villain
       into battle, and will return the name of whoever wins!
    """

    try:
        for possession in supervillain.getLoot():
            if superhero.isVulnerableTo(possession):
                return supervillain.getName()
        return superhero.getName()
    except Exception as e:
        # Draw, so no-one won!
        return "No-one, because %s" % e

superman = Superhero(name="Superman", weakness="kryptonite")

print("Is it a bird? Is it a plane? No, it's %s!!!" % superman.getName())

lex = Supervillain(name="Lex Luther")

print("%s will battle %s. The winner is %s" \
  % (superman.getName(), lex.getName(), \
     battle(superman, lex) ) )

print("Lex steals some krytonite...")
lex.steal("kryptonite")

print("They battle again... The winner is %s" \
   % battle(superman, lex))



We can get help on the module using help

In [3]:
help(superhero)
Help on module superhero:

NAME
    superhero

DESCRIPTION
    This module provides a set of classes for creating superheros
    and supervillains. Have fun!
    
    Author - Christopher Woods
    License - BSD

CLASSES
    builtins.object
        Superhero
        Supervillain
    
    class Superhero(builtins.object)
     |  Superhero(name, weakness)
     |  
     |  This class allows you to create your own Superhero
     |  
     |  Methods defined here:
     |  
     |  __init__(self, name, weakness)
     |      Construct a superhero with the specified name and the 
     |      specified weakness
     |  
     |  getName(self)
     |      Return the name of the superhero
     |  
     |  getWeakness(self)
     |      Return the weakness of the superhero
     |  
     |  isVulnerableTo(self, item)
     |      Return whether or not this superhero is 
     |      vulnerable to 'item'
     |  
     |  setName(self, name)
     |      Set the name of the superhero
     |  
     |  setWeakness(self, weakness)
     |      Set the weakness of the superhero
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors defined here:
     |  
     |  __dict__
     |      dictionary for instance variables (if defined)
     |  
     |  __weakref__
     |      list of weak references to the object (if defined)
    
    class Supervillain(builtins.object)
     |  Supervillain(name)
     |  
     |  This class allows you to create your own supervillain
     |  
     |  Methods defined here:
     |  
     |  __init__(self, name)
     |      Initialize self.  See help(type(self)) for accurate signature.
     |  
     |  getLoot(self)
     |      Return all of the loot that this villain has stolen
     |  
     |  getName(self)
     |      Return the name of the villain
     |  
     |  setName(self, name)
     |      Set the name of the villain
     |  
     |  steal(self, item)
     |      Tell the villain to steal 'item'
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors defined here:
     |  
     |  __dict__
     |      dictionary for instance variables (if defined)
     |  
     |  __weakref__
     |      list of weak references to the object (if defined)

FUNCTIONS
    battle(superhero, supervillain)
        This function will pitch the superhero and villain
        into battle, and will return the name of whoever wins!

DATA
    lex = <superhero.Supervillain object>
    superman = <superhero.Superhero object>

FILE
    /home/travis/virtualenv/python3.7.1/lib/python3.7/site-packages/superhero.py


This documentation comes from the class and function documentation put into the file.

You can also use the data, classes and functions in the file, e.g.

In [4]:
ironman = superhero.Superhero(name="Iron Man", weakness="rust")
In [5]:
superhero.battle(ironman, superhero.lex)
Out[5]:
'Iron Man'
In [6]:
superhero.lex.steal("rust")
In [7]:
superhero.battle(ironman, superhero.lex)
Out[7]:
'Lex Luther'

One thing to note is that all of the classes, functions and data in the script has been imported into its own namespace, named after the script (e.g. superhero). We can import the file and put all names into the current namespace using

In [8]:
from superhero import *
In [9]:
battle(ironman, lex)
Out[9]:
'Lex Luther'

While any python script can be imported as a module, there are a few conventions you should follow that will make your module easier for others to use.

  • Add documentation to the module. As you can see, there is a docstring at the top of superhero.py, which is the first thing written out by help(). This should provide an overview of the module.
  • Avoid actually running any code or creating any variables. The current superhero.py is bad as it does this, which is why you see "Is it a bird..." printed when you import it!

The way to avoid creating any variables or running code is to let the script detect when it is being imported, and to not create any variables if that is the case.

You can detect if your Python script is not being imported using

if __name__ == "__main__":
    print("I am not being imported.")
In [10]:
if __name__ == "__main__":
    print("I am not being imported")
I am not being imported

To show how this works, there is a superhero2.py script, which is identical to superhero.py, except all code that should not be run on import is hidden inside the if __name__ == "__main__": block.

In [11]:
import superhero2

By using if __name__ == "__main__": we have prevented superhero2.py from printing anything out when it is imported, and have also prevented it from creating the variables lex and superman.

In [12]:
lines = inspect.getsource(superhero2)
print(lines)
"""
This module provides a set of classes for creating superheros
and supervillains. Have fun!

Author - Christopher Woods
License - BSD
"""

class Superhero:
    """This class allows you to create your own Superhero"""
    def __init__(self, name, weakness):
        """Construct a superhero with the specified name and the 
           specified weakness
        """
        self.setName(name)
        self.setWeakness(weakness)

    def setName(self, name):
        """Set the name of the superhero"""
        self._name = name

    def setWeakness(self, weakness):
        """Set the weakness of the superhero"""
        self._weakness = weakness

    def getName(self):
        """Return the name of the superhero"""
        return self._name

    def getWeakness(self):
        """Return the weakness of the superhero"""
        return self._weakness

    def isVulnerableTo(self, item):
        """Return whether or not this superhero is 
           vulnerable to 'item'"""
        return self.getWeakness().lower() == item.lower()

class Supervillain:
    """This class allows you to create your own supervillain"""
    def __init__(self, name):
        self.setName(name)
        self._loot = []

    def setName(self, name):
        """Set the name of the villain"""
        self._name = name

    def getName(self):
        """Return the name of the villain"""
        return self._name

    def steal(self, item):
        """Tell the villain to steal 'item'"""
        self._loot.append(item)

    def getLoot(self):
        """Return all of the loot that this villain has stolen"""
        return self._loot

def battle(superhero, supervillain):
    """This function will pitch the superhero and villain
       into battle, and will return the name of whoever wins!
    """

    try:
        for possession in supervillain.getLoot():
            if superhero.isVulnerableTo(possession):
                return supervillain.getName()
        return superhero.getName()
    except Exception as e:
        # Draw, so no-one won!
        return "No-one, because %s" % e

if __name__ == "__main__":
    superman = Superhero(name="Superman", weakness="kryptonite")

    print("Is it a bird? Is it a plane? No, it's %s!!!" % superman.getName())

    lex = Supervillain(name="Lex Luther")

    print("%s will battle %s. The winner is %s" \
       % (superman.getName(), lex.getName(), \
          battle(superman, lex) ) )

    print("Lex steals some krytonite...")
    lex.steal("kryptonite")

    print("They battle again... The winner is %s" \
        % battle(superman, lex))


You can see this by running the superhero2.py script directory, e.g. using

In [13]:
! python ./code/superheros/superhero2.py
Is it a bird? Is it a plane? No, it's Superman!!!
Superman will battle Lex Luther. The winner is Superman
Lex steals some krytonite...
They battle again... The winner is Lex Luther

Exercise

1

Use the "New Text File" option in the Jupyter Home to create a new python text file called morse.py. Copy the below class into this file.

class Morse:
    def __init__(self):
        self._letter_to_morse = {'a':'.-', 'b':'-...', 'c':'-.-.', 'd':'-..', 'e':'.', 'f':'..-.',
                   'g':'--.', 'h':'....', 'i':'..', 'j':'.---', 'k':'-.-', 'l':'.-..', 'm':'--',
                   'n':'-.', 'o':'---', 'p':'.--.', 'q':'--.-', 'r':'.-.', 's':'...', 't':'-',
                   'u':'..-', 'v':'...-', 'w':'.--', 'x':'-..-', 'y':'-.--', 'z':'--..',
                   '0':'-----', '1':'.----', '2':'..---', '3':'...--', '4':'....-',
                   '5':'.....', '6':'-....', '7':'--...', '8':'---..', '9':'----.',
                   ' ':'/' }

        self._morse_to_letter = {}
        for letter in self._letter_to_morse.keys():
            self._morse_to_letter[ self._letter_to_morse[letter] ] = letter

    def encode(self, message):
        morse = []
        for letter in message:
            morse.append( self._letter_to_morse[letter.lower()] )
        return morse

    def decode(self, morse):
        message = []
        for code in morse:
            message.append( self._morse_to_letter[code] )
        return "".join(message)

Add documentation to this class, and to the module. Next, import the module and get help using the commands

import morse
help(morse)

Does your documentation make sense?

2

Create some checks of your module that should not be run when the module is imported (i.e. only run directly). The checks should be, e.g.

morse = Morse()

    for message in ["Hello world", "something to encode", "test message"]:
        test = morse.decode( morse.encode(message) )

        if message.lower() == test: 
            print("Success: %s" % message)
        else:
            print("Failed: %s" % message)

Validate that the check doesn't run on import using

import morse

Validate that the check runs from the command line using

! python morse.py

Key Points:

  • We can package useful bits of code that we want to use and re-use into modules.
  • We import these modules or libraries in exactly the same way as other libraries, numpy, re.