What is the point of modules?
Modules are a mechanism to structure an application into distinct, reusable parts. E.g. a command line utility could factor out the code that does the argument parsing into a separate module that can then be used in other command line tools as well. Since a module has a clear public interface and can hide implementation details within the module, the overall application also becomes less tightly coupled.
Can I swap out a module for another module with the same interface?
Not necessarily, and that is not the point of modules. However, there are various techniques to implement modules. One is to define a structure of function pointers that defines the type of the whole module. When a module is loaded, it provides an instance of this module. This technique is frequently used to mimic modules in JavaScript:
// a mathModule may be anything that supplies "add" and "sub" functions
var mathModule = (function () {
var privateVar = "secret";
function add(a, b) { return a + b; }
function sub(a, b) { return a - b; }
function privateFunction() { return "very" + privateVar; }
// return an “object” (Python: dict) that defines the public interface
return {
add: add,
sub: sub,
};
})();
This technique can also be adapted to C to provide real modules, and can be easily translated to Python. However, neither language has lambdas and C lacks closures, so the translated example would be more restricted.
Since we are dealing with function pointers here, we have an important level of indirection. In principle, we could have multiple instances of the same module interface at once, and we can easily switch to a different implementation if it offers the same interface.
This sounds very much like object-oriented programming, but a few important details are missing: for OOP, the instance structure must also contain data in addition to methods, and must pass the instance structure as an argument back into any method that was invoked. I discuss creating a JavaScript object system out of closures in a blog post in more depth.
Do Python's modules work like this?
Kind of. In fact, Python's modules act very much like a regular object except that we don't have a self
argument. However, Python misses the part where we actually define a clear interface for our module. Where the above JavaScript example can explicitly choose whether to expose a function through the public interface, Python makes any symbol in file scope accessible when import
ing a module. There are tricks that limit this issue (such as defining functions in helper modules and then selectively importing the public interface into the module's __init__.py
), but in Python we end up defining the interface through our documentation, not through the code.
So, long story short, Python has namespaces that it calls “modules”, but don't offer any encapsulation.
So, how can I inherit a module?
The concept of “inheritance” makes no sense when discussing modules, as it's more of an OOP term. There, “Subclass
inherits from Base
” means (a) that Subclass
is a subtype of Base
and (b) that Subclass
instances can somehow use the implementation defined by Base
. As you might know, reusing the implementation can be done via two mechanisms: implementation inheritance (where the method lookup mechanism for Subclass
is initialized with the methods for Base
) and composition (where Subclass
delegates to a Base
instance).
With the module-as-dict encoding, this can be illustrated as such:
# The original module.
# Can be instantiated like `mathModule = mathModule_load()`
def mathModule_load():
private_var = "secret"
def add(a, b): return a + b
def sub(a, b): return a - b
def private_function(): return "very" + private_var
return {
'add': add,
'sub': sub,
}
# Inherit the mathModule via composition
# Overrides "add" to type-check for int arguments
def checkedMathModule_load():
base = mathModule_load() # the module instance we inherit
def add(a, b):
if type(a) is not int or type(b) is not int:
raise TypeError("give me ints!")
return base['add'](a, b)
def sub(a, b):
return base['sub'](a, b)
# return our own module instance that meets the expected interface
return {
'add': add,
'sub': sub,
}
# Inherit the mathModule by changing the module definition
# Overrides "add" to emit debug info
def chattyMathModule_load():
module = mathModule_load() # the module instance we inherit
original_add = module['add']
def my_add(a, b):
print("DEBUG: add(%d, %d)" % (a, b))
return original_add(a, b)
module['add'] = my_add
return module
OK, can we now do that with built-in modules?
*grumble* Python may formally be multi-paradigm, but it very clearly favours OOP solutions to problems like this. We can rework the above example to use Python's built-in modules, but it isn't pretty. Note that Python's modules don't have a comparable concept of “instances”, as modules are global.
Instead of the explicit dict
that stores the function of the module, we will use regular Python syntax to add symbols to the file scope.
The modules are organized in the following file hierarchy:
mathModule/
__init__.py
detail.py
checkedMathModule/
__init__.py
detail.py
chattyMathModule/
__init__.py
detail.py
mathModule/__init__.py
:
from .detail import add, sub
del detail
mathModule/detail.py
:
private_var = "secret"
def add(a, b):
return a + b
def sub(a, b):
return a - b
def private_function():
return "very" + private_var
checkedMathModule/__init__.py
:
# this module offers the same interface as mathModule
from .detail import add, sub
del detail
checkedMathModule/detail.py
:
import mathModule as base
def add(a, b):
if type(a) is not int or type(b) is not int:
raise TypeError("give me ints!")
return base.add(a, b)
def sub(a, b):
return base.sub(a, b)
chattyMathModule/__init__.py
:
from mathModule import *
# now import the function we want to override
from .detail import my_add as add
del detail
chattyMathModule/detail.py
:
from mathModule import add as original_add
def my_add(a, b):
print("DEBUG: add(%d, %d)" % (a, b))
return original_add(a, b)
I am not sure that the del detail
won't break anything, but without it users could do mathModule.detail.private_function()
and get something 'verysecret'
, which completely breaks encapsulation.
These techniques kind of work, but are a lot of unnecessary effort for something that could be done much more easy with regular objects. Also, Python is special because modules are represented as module objects that can be passed around as values, whereas the module system of most languages is far more static. These techniques are not representative of modular programming, but rather of a “I want to do OOP without using the OOP parts of my language” mindset that is not constructive for anything except understanding OOP better.