What you ask is not directly possible. There is no parameterisation capability built in to Python's module system. If you think about it, it's not clear how such a proposal ought to work: if modules A
and B
both import module M
, but they supply different parameters, which parameter is used when M
is imported? Is it imported twice? What would that mean for module-level configuration (as in logging
)? It gets worse if a third module C
attempts to import M
without parameters. Also, the "open-world" idea that you could override any import
statement from the outside violates the language-design principle that "the code you wrote is the code that ran".
Other languages have incorporated parameterised modules in a variety of ways (compare Scala's object model, ML's modules and signatures, and - stretching it - C++'s templates), but it's not clear that such a feature would be a good fit for Python. (That said, you could probably hack something resembling parameterised modules using importlib
if you were determined and masochistic enough.)
Python does have very powerful and flexible capabilities for dynamic dispatch, however. Python's standard, day-to-day features like functions, classes, parameters and overriding provide the basis for this support.
There are lots of ways to cut the cake on your example of a function whose behaviour is configurable by its client.
A function parameterised by a value:
def say_hi(greeting):
print("Message: " + greeting)
def main():
say_hi("Hello")
A class parameterised by a value:
class Greeter:
def __init__(self, greeting):
self.greeting = greeting
def say_hi(self):
print("Message: " + self.greeting)
def main():
Greeter("Hello").say_hi()
A class with a virtual method:
class Greeter:
def say_hi(self):
print("Message: " + self.msg())
class MyGreeter(Greeter):
def msg(self):
return "Hello"
A function parameterised by a function:
def say_hi(greeting):
print("Message: " + greeting())
def make_greeting():
return "Hello"
def main():
say_hi(make_greeting)
There are more options (I'm avoiding the Java-y example of objects invoking other objects) but you get the idea. In each of these cases, the selection of the behaviour (the passing of the parameter, the overriding of the method) is decoupled from the code which uses it and could be put in a different file. The right one to choose depends on your situation (though here's a hint: the right one is always the simplest one that works).
Update: in a comment you mention that you'd like an API which sets up the dependency at the module-level. The main problem with this is that the dependency would be global - modules are singletons, so anyone who imports the module has to use the same implementation of the dependency.
My advice is to provide an object-oriented API with "proper" (per-instance) dependency injection, and provide top-level convenience functions which use a (configurable) "default" set-up of the dependency. Then you have the option of not using the globally-configured version. This is roughly how asyncio
does it.
# flexible object with dependency injection
class Greeter:
def __init__(self, msg):
self.msg = msg
def say_hi(self):
print("Message: " + self.msg)
# set up a default configuration of the object to be used by the high-level API
_default_greeter = Greeter("Hello")
def configure(msg):
global _default_greeter
_default_greeter = Greeter(msg)
# delegate to whatever default has been configured
def say_hi():
_default_greeter.say_hi()