9) e. Modules vs Packages

Earlier you have learned that any Python file is considered a module. You separate your code thematically into different files to keep it organized. But if your project is very big? In that case you can go a step further and group your modules into packages.

A package is simply a folder that contains Python modules (.py files) . It might contain other sub-folders, aka sub-packages as well which would contain more Python modules.

Importing a package:

Normally, when you import a Python module, you import only one file with the extension .py . When you import a Python package you essentially import a folder containing a collection of modules rather than a single module.

If you think more about this, what if the folder that you’re trying to import contained many sub-folders? And you wanted to access those sub-folders. Would you need to import each sub-package before you can use it?

The answer is – you don’t. Importing each sub-folder would be tedious and Python doesn’t expect you to do that. Once you import a package, you can access all sub-packages by using the “dot operator ( . )” while importing / calling the required module / function.

Let’s suppose that there is a package named “jack” which contains multiple sub-packages. You import jack into your Python file and then attempt to call a function “up()”. Take a look at the following code which calls a function present within multiple subpackages.

As you can see, once you import a package you can access its sub-packages and individual modules using the dot operator.

Different ways of importing:

In the above example, you could have imported the package jill with import jack.and.jill and then called the function starting from jill i.e., your call would have been jill.went.up().

Try to compare the different ways in which you can import packages/modules and how it affects the code used to call their containing modules & functions.

core.utilities. = Packages, AKA folders

calc / logs / colors = Modules, AKA python files

add(), print_logs(), screen_test() = Functions

A couple of things to notice in these examples.

  • The first method of importing the core package and then calling several subpackages inside it results in lengthy code that becomes tedious to type as your program gets bigger. To call any function you need to navigate through multiple packages. This isn’t a recommended way of importing packages.
  • The fourth method imports the exact functions from various modules. The problem with this method is that your code will soon become a mess of lots of functions from various sources and may result in name clashes. It also results in hard-to-read code because someone reading your code (or if you were to read your code after a few weeks) wouldn’t understand what exactly the method “display()” or screen_test() does. This is because there is no context tied with the functions anymore. This is also not a recommended way of importing packages/modules.
  • The third method imports exactly the modules that need to be used. This allows you to call various functions in those modules and at the same time have some context to those calls. For example, you read colors.screen_test() and can immediately guess this function will do some kind of tests related to colors on your screen. Having a little context to your calls goes a long way in terms of readability later on. This is a preferred way of importing and using modules.
  • The second method imports exactly the packages that we need and then uses that package to access the various modules and their functions. This too provides a good balance between code readability and having sufficient context. This is another good way of importing and using packages.

Best practices while importing packages/modules:

There are no official best practices recommended for importing. However, it is common sense to:

  • Import packages: uptil the one just before the module:
    Example: import package1.package2.package3
    And then later you can call package3.module1.function()
  • Import modules : When their names have enough meaning by themselves:
    Example: from datetime import datetime
    When your module name is sufficient to give you a context while reading your code then you just import the module rather than the entire package. In this case, you can directly use datetime, as opposed to using datetime.datetime.
  • Import classes: when you will be using that class extensively. Example: from Pathlib import Path
    Over here you don’t import Pathlib because you specifically want to use the class Path only.
  • Import X as Y: when X is a very lengthy name.
    Example: import package_with_all_tools as AT
    If you ever come across a package name that feels too long feel free to use an alias name while importing.

There are no hard and fast rules for importing but the main idea is to improve code readability through trial and error. So if you find yourself constantly typing multiple packages then consider importing the subpackage rather than the main package or using aliases.

The __init__.py file:

If you create an __init__.py file inside any folder, then Python will recognize that folder as a package. Even if the __init__.py file is completely empty, your folder is still considered a package.

However, from Python 3.3+ the empty __init__.py file was made optional for packages. In other words, you could import any random folder into your code and Python would treat it as a package.

This has caused much confusion where you will hear people saying that you need to add the __init__.py file if you are creating a package. However, if you’re on Python 3.3+ it’s not necessary.

The __init__.py file will get executed at the time of package import. However, that said, it is beyond the scope of this course to cover this file in any more details. For now you need to know that this file gets executed whenever its package is imported. That will help you as you work on projects.

Summarizing: What is the difference between a package and a module?

Imports multiple python files.Imports a single python file.
Once a package is imported you can access nested packages as well (sub-packages).Is a single python file.
__init__.py file gets executed when a package is imported.The python module gets executed when it is imported.
Added automatically to PYTHONPATH after creation of __init_.py file.Needs to be manually added to PATH or PYTHONPATH.
To run a package directly by name it must contain a __main__.py file which will get executed. (Ex. python -m packagename1).You can run a module by calling it directly by name from the terminal. (Ex. python filename1).