What makes a module reusable?

In order for a module or package to be reusable, it has to meet the following requirements:

  • It must function as a standalone unit
  • If your package is intended to be included as part of the source code for another system, you must use relative imports to load the other modules within your package
  • Any external dependencies must be clearly noted

If a module or package does not meet these three requirements, it would be very hard, if not impossible, to reuse it in other programs. Let's now take a closer look at each of these requirements in turn.

Functioning as a standalone unit

Imagine that you decide to share a module named encryption, which performs text encryption using public/private key pairs. Another programmer then copies this module into their program. When they try to use it, however, their program crashes with the following error message:

ImportError: No module named 'hash_utils'

The encryption module may have been shared, but it was dependent on another module within the original program (hash_utils.py) that wasn't shared, and so the encryption module by itself is useless.

The solution to this problem is to combine the module you want to share with any other modules it may depend upon, putting the modules together into a package. You then share the package, rather than the individual module. The following illustration shows how this might be done:

Functioning as a standalone unit

In this example, we have created a new package named encryptionlib and moved both the encryption.py and hash_utils.py files into this package. Of course, this requires you to refactor the rest of your program to allow for the new position of these modules, but it does then allow you to reuse your encryption logic in other programs.

Note

While it can be a nuisance having to refactor your program in this way, the result is almost always an improvement on your original program. Putting dependent modules together into a package helps to improve the overall organization of your code.

Using relative imports

Continuing with the example from the previous section, imagine that you want to use your new encryptionlib package as part of another program, but don't want to make it publically available as a separate package. In this case, you can simply include the entire encryptionlib directory as part of your new system's source code. When you do this, however, you can run into problems if your modules don't use relative imports. For example, if your encryption module is dependent on the hash_utils module, then the encryption module is going to include an import statement referring to the hash_utils module. However, the resulting package cannot be reused if the encryption module imports hash_utils in any of the following ways:

import hash_utils
from my_program.lib import hash_utils
from hash_utils import *

All of these import statements will fail because they assume that the hash_utils.py file is at a particular fixed point in your program's source code. Any assumption about the position of a dependent module within the program's source code will limit the reusability of the package as you can't then move the package to a different place and expect it to work. Given the requirements of the new project, you will often have to store packages and modules in a different place from where they were originally developed. For example, perhaps the encryptionlib package needs to be installed in a thirdparty package along with all the other reused libraries. Using absolute imports, your package will fail because the location of the modules within it will have changed.

Note

This doesn't apply if you publish your package and then install it into your Python site-packages directory. However, there are many situations where you don't want to install a reusable package inside the site-packages directory, and so you will need to be careful about relative imports.

To solve this problem, make sure that any import statements within a package that refer to other modules within the same package always use a relative import. For example:

from . import hash_utils

This will allow your package to work no matter where in the Python source tree the package has been placed.

Noting external dependencies

Imagine that our new encryptionlib package makes use of the NumPy library we encountered in the previous chapter. Perhaps hash_utils imports some functions from NumPy and uses them to quickly calculate a binary hash of a list of numbers. Even though NumPy was installed as part of the original program, you can't assume that the same is true of the new program: if you were to install the encryptionlib package into a new program and run it, it would eventually fail with the following error:

ImportError: No module named 'numpy'

To prevent this from happening, it is important that anyone wanting to reuse your module is aware of the dependency on a third-party module and knows exactly what needs to be installed for your module or package to function. An ideal place to include this information is in the README file or other documentation for the module or package you are sharing.

Note

If you are using an automated deployment system such as setuptools or pip, these tools have their own way of identifying your package's requirements. It is still a good idea, though, to list the requirements in your documentation so your users will be aware of them before the package is installed.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.190.156.93