In Python, a module is a file containing Python definitions and statements that can be imported and used in other Python programs. Modules are a key feature of the language that allow you to organize your code and reuse it in multiple places.
One reason why modules are often overlooked is because they are so easy to use that many Python programmers take them for granted. However, modules provide several important benefits that are not always available in other (OOP) programming languages.
Modules allow you to encapsulate related functionality into a single file that can be reused in multiple projects. This makes it easy to write code that can be shared among different applications, without having to copy and paste the same code over and over.
Circular Dependencies: In many object-oriented languages, circular dependencies between classes can be difficult to manage. With modules in Python, circular dependencies can be resolved by importing modules at runtime, rather than at compile time.
By breaking your code down into smaller modules, you can create a more modular design that is easier to understand, test, and modify. This is especially important in larger projects, where it can be difficult to keep track of all the different pieces of code.
There are some unique patterns that are possible because of Python Module infrastructure:
The Singleton pattern is used to prevent construction more than once, and to be able to pass it as arguments to other functions. Python modules are objects and can be used as singletons, as each time a module is imported, it returns the same module object. By using modules as singletons, you can create a global object that can be used throughout your program.
Common dependency injection provides dependencies to classes as initializer arguments:
from mailer import send_mail class Mailer: def __init__(self, send_mail, from_address): self.send_mail = send_mail # store the function self.from_address = from_address def send(self, address, message): self.send_mail(to_address, from_address, message)
The advantage for this is that for testing, the mailer could be replaced (mock):
In Python we could also use the function directly without dependency injection:
class Mailer: def __init__(self, from_address): self.from_address = from_address def send(self, address, message): send_mail(to_address, from_address, message)
Again, because of Pythons module infrastructure, we can simply import the class, and override a function before instantiating for testing purposes.
import Mailer mailer = Mailer() mailer.send_mail = new_send_mail mailer.send("email@example.com")
Modules offer a way to define a distinct namespace for various code components. This helps to avoid naming conflicts between different parts of your program and makes it simpler to organize and maintain your code. In other programming languages, the concept of taxonomy and namespacing is often achieved through the use of classes. However, in Python, modules should be utilized for this purpose.
Every Python code unit (modules, functions, classes, methods) can have a docstring.
The location of module docstring can either be a file or init.py.
Reason for adding a docstring to a module is not just to show it to anyone reading the code,
but can be easily displayed by using the help function on a module,
import itertools ; help(itertools)