Modules and packages are essential components of Python's modular programming paradigm, enabling developers to organize, structure, and manage their code efficiently. They offer a powerful mechanism for building complex applications, allowing developers to create a well-organized ecosystem of modules and subpackages. In this dynamic Python landscape, modules and packages play a vital role in promoting code reusability, collaboration, and the development of scalable software solutions.
Modules
Modules provide a powerful mechanism for structuring and organizing code. They allow us to group related functions, classes, and variables into separate files, making our code more manageable and reusable. Modules help avoid naming conflicts, promote code isolation, and enable collaboration by allowing different parts of a program to be developed independently.
There are several ways to import modules, allowing us to control how we access the functions, classes, and variables within a module. Here are the most common ways to import modules.
Import the Whole Module. The simplest way to import a module is by using the import
keyword followed by the module name. This makes all the functions, classes, and variables in the module available under the module's name.
import my_module
result = my_module.some_function()
Import Specific Functions or Variables. We can also import specific functions or variables from a module. This way, we can use them directly without referring to the module name.
from my_module import some_function, my_variable
result = some_function()
Import with an Alias. We can provide an alias for a module or specific items within it. This can make our code more concise and readable, especially when dealing with modules with long names.
import my_module as mm
result = mm.some_function()
We can also use an alias when importing specific functions or variables.
from my_module import some_function as sf
result = sf()
Import Everything. While we can use a wildcard (*
) to import all items from a module, it is generally not recommended. This can make our code less readable and lead to naming conflicts if multiple modules have items with the same name.
from my_module import *
result = some_function()
Conditional Import Using importlib
. In some situations, we might need to import a module conditionally at runtime. The importlib
library provides functions to do this dynamically.
import importlib
if some_condition:
my_module = importlib.import_module('my_module')
result = my_module.some_function()
These are the primary ways to import modules in Python. The choice of method depends on our specific needs and coding style. It's generally a good practice to import only what we need to keep our code clean and avoid naming conflicts, especially in larger projects.
Built-in modules
Python provides built-in pre-existing libraries. These modules offer a wide range of functionalities and tools for various tasks, such as mathematical operations, file handling, network communication, data manipulation, and more. They are an essential part of the standard library, and developers can use them without needing to install any additional packages.
math 🧮
The math
module offers a comprehensive set of mathematical functions, including trigonometric, logarithmic, and arithmetic operations. It's useful for tasks that involve complex mathematical calculations.
>>> import math
>>> math.sqrt(25)
5
>>> math.sin(math.pi / 2)
1.0
random 🎲
The random
module allows us to generate pseudo-random numbers. It's handy for simulations, games, and other scenarios where randomness is required.
>>> import random
>>> print(random.randint(1, 10))
4
💡 We have already used the random module when implementing "Cows and Bulls" game!
datetime 📆
The datetime
module provides classes for working with dates and times. We can use it to manipulate, format, and calculate dates and times.
>>> from datetime import datetime
>>> datetime.now()
datetime.datetime(2023, 10, 31, 20, 0, 14, 205257)
>>> now = datetime.now()
>>> print(now)
2023-10-31 20:00:37.806677
os 💾
The os
module allows us to interact with the operating system, including functions to manage files and directories, check file existence, and execute system commands.
>>> import os
>>> os.listdir('.')
['site-packages-3', 'Welcome.md', 'site-packages-2', 'main2.py', 'site-packages', 'main.py', 'Examples']
sys 💻
The sys
module provides access to system-specific parameters and functions. It's commonly used to manipulate the Python interpreter and command-line arguments.
>>> import sys
>>> sys.platform
'ios'
json 📄
The json
module is used for encoding and decoding JSON (JavaScript Object Notation) data. It's invaluable for working with APIs, configuration files, and data interchange.
>>> import json
>>> data = {'name': 'John', 'age': 30}
>>> json.dumps(data)
'{"name": "John", "age": 30}'
socket 🚀
The socket
module allows us to create and manage network connections. We can use it to implement network protocols, client-server applications, and more.
>>> import socket
>>> server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> server_socket.bind(('127.0.0.1', 8080))
re ⠻
The re
module provides support for regular expressions. It allows us to search, match, and manipulate text using complex pattern matching.
>>> import re
>>> pattern = r'\d{3}-\d{2}-\d{4}'
>>> text = f'My Social Security Number is 123-45-6789.'
>>> re.search(pattern, text)
<re.Match object; span=(29, 40), match='123-45-6789'>
Custom modules
Custom modules in Python are user-defined collections of functions, classes, and variables encapsulated within individual Python files with a .py
extension. They enable developers to create their own libraries of functions tailored to specific tasks, making code more modular and maintainable.
Creating a custom module is a straightforward process. A custom module is essentially a Python script. To create one, start by writing our desired functions or classes in a file and save it with a .py
extension. For example, if we want to create a module for mathematical operations, we can write functions for addition, subtraction, multiplication, etc.
Once we've defined our module's content, we can import and use it in other scripts by simply using the import
statement.
How can we use modules in our case? Let's recall our Car
class from the previous post.
We already have defined Car
and Ambulance
classes there. It is likely to happen when the project grows, additional vehicles will pop up. Let's imagine there is a new requirement to introduce a TowTrack
vehicle. The class hierarchy diagram is as follows.
We are already familiar with Car
and Ambulance
code.
class Car:
def __init__(self, make: str, model: str):
self.__make: str = make
self.__model: str = model
def __str__(self) -> str:
return f'{self.__make} {self.__model}'
def __repr__(self) -> str:
return f'{self.__make} {self.__model}'
def drive(self):
print(f'Driving {self.__make}: {self.__model}')
def is_model(self, model: str) -> bool:
return model in self.__model
class Ambulance(Car):
def __init__(self, make: str, model: str, sound: str = f'Wee woo!!!'):
super().__init__(make, model)
self.__sound: str = sound
def siren(self):
print(f'Siren: {self.__sound}')
Here comes the new vehicle type, a TowTrack
.
class TowTruck(Car):
def __init__(self, make: str, model: str):
super().__init__(make, model)
def tow(self, car: Car):
print(f'Driving {self.__make}: {self.__model} and towing: {car}')
The new vehicle provides the ability to tow other cars, which might be pretty crucial when they break down. Now it might be a good choice to put all those cars in one separate module. Let's name it vehicle
.
Now in the main script, we just have to import and use them.
from vehicle import Ambulance, Car, TowTruck
def main():
vehicles = [Car(f'Toyota', 'Camry'),
Ambulance(f'Toyota', 'Corolla'),
TowTruck(f'Toyota', f'Prius')]
print(vehicles)
if __name__ == "__main__":
main()
Running the main script prints us a list of cars.
❯ python3 main.py
[Toyota Camry, Toyota Corolla, Toyota Prius]
Packages
Packages in Python are a way to organize and structure our code into directories and subdirectories, providing a hierarchical structure for our modules. They are used to group related modules together and make it easier to manage large projects.
What is a Package? A package is a directory that contains a special __init.py__
file (which can be empty) and one or more module files. The __init.py__
file indicates to Python that the directory should be treated as a package, and it can also contain package-level initialization code. To create a package, we simply need to create a directory and place an __init.py__
file inside it. We can have multiple modules within this directory.
my_package/
├── __init__.py
├── module1.py
├── module2.py
Here, my_package
is the package, and module1.py
and module2.py
are modules within the package. We can import modules from a package using dot notation. For example, if we have a module named module1
within the my_package
package, we can import it as follows.
from my_package import module1
We can also import specific functions, classes, or variables from the module using dot notation.
from my_package.module1 import my_function
Nested Packages. Packages can be nested within other packages, creating a hierarchical structure. For example, we can have a package within a package.
my_package/
├── __init__.py
├── module1.py
└── subpackage/
├── __init__.py
├── module3.py
We can access modules from the subpackage as follows.
from my_package.subpackage import module3
Package Initialization. The __init.py__
file in a package can contain the initialization code that will be executed when the package is imported. This is useful for setting package-wide configurations or performing setup tasks. We can also define variables or functions within the __init.py__
file to make them accessible as package-level resources.
Within a package, we can use relative imports to import modules or submodules from the same package. For example, if we have a module module4
in the same package as module1
, we can use a relative import.
from . import module4
The dot .
represents the current package.
__all__
Attribute. We can specify a list of modules or module names in the __init.py__
file using the __all__
attribute. This attribute tells Python which modules are considered public and should be imported when someone uses a wildcard import (from package import *
).
Packages help keep our code organized and modular especially when managing complex projects.
We can make use of packages in our project as well. Let's create the transporation
package. Then move vehicle.py
there and create the __init.py__
file.
When exploring __dunder__
methods, we created ParkingLot
class. Now we are going to use it again. If you need a refresher on __dunder__
methods, grab the link.👇👇👇
Let's create area.py
module inside transporation
package and place ParkingLot
class inside.
Now we can make a small update to the __init.py__
file.
__all__ = ['area', 'vehicle']
Once we are done, the project layout should be as follows.
transportation/
├── __init__.py
├── area.py
├── vehicle.py
main.py
Now we can use our freshly created package in the main.py
script.
We are ready to run it! 🚀
❯ python3 main.py
Space occupancy:
Spot 0 is BUSY, parked car: Toyota Camry
Spot 1 is BUSY, parked car: Toyota Corolla
Spot 2 is BUSY, parked car: Toyota Prius
Spot 3 is FREE
Spot 4 is FREE
Spot 5 is FREE
Spot 6 is FREE
Spot 7 is FREE
Spot 8 is FREE
Spot 9 is FREE
Summary
Modules and packages are indispensable tools for structuring and managing code. Modules allow developers to encapsulate and reuse functions, classes, and variables, enhancing code readability and maintainability. Packages, on the other hand, offer a hierarchical structure for organizing modules, making them essential for handling more extensive projects and promoting a modular and collaborative development approach. Together, modules and packages provide a powerful foundation for constructing well-organized, scalable, and efficient Python applications, fostering code reusability, collaboration, and the creation of easily maintainable software solutions.