2

My python module uses some functions from another module, but I have several implementations of that module interface. How to point out, which one to use?

Simple example:

A.py:

import B
def say_hi()
   print "Message: " + B.greeting()

main.py:

import A(B=my_B_impl)
A.say_hi()

my_B_impl.py:

def greeting():
   return "Hallo!"

output:

Message: Hallo!
Ramil
  • 31
  • 2
  • Sounds like you want to do _parameterised modules_. That's not supported by Python directly (well, it probably is if you're into metaprogramming), though you can get 90% of the way there with classes. – Benjamin Hodgson Nov 22 '15 at 14:00
  • Unfortunately classes aren't suitable for me in that case – Ramil Nov 22 '15 at 14:04
  • In what respect are they unsuitable? – Benjamin Hodgson Nov 23 '15 at 00:34
  • May be import module dynamically by a path to it? https://stackoverflow.com/questions/67631/how-to-import-a-module-given-the-full-path/57843421#57843421 – Andry Sep 08 '19 at 21:49

2 Answers2

1

In python this could be most elegantly done with inheritance:

A.py:

import B
class SayHi(object):
    b = B
    def say_hi(self):
       print "Message: " + self.b.greeting()

my_B_impl.py:

class AlternativeHi(object):
    def greeting(self):
       return "Hallo!"

main.py:

import A
from my_B_impl.py import AlternativeHi
class MyHi(SayHi):
    b=AlternativeHi
a=MyHi()
MyHi.say_hi()

output:

Message: Hallo!

You can also use the factory pattern to avoid explicit declaration of class AlternativeHi and MyHi:

A.py

from B import greeting
class SayHi(object):
    def __init__(self,*args,**kwargs):
        self.greeting = greeting
    def say_hi(self):
       print "Message: " + self.greeting()

def hi_factory(func):
    class CustomHi(SayHi):
        def __init__(self,*args,**kwargs):
            result = super(CustomHi, self).__init__(*args, **kwargs)
            self.greeting = func
    return CustomHi

my_B_impl.py:

def greeting(self):
    return "Hallo!"

main.py:

form A import hi_factory
from my_B_impl import greeting
a = hi_factory(greeting)
a.say_hi()
Sebastian Wozny
  • 16,943
  • 7
  • 52
  • 69
  • Really interesting. What about if it is not a Class ? Just a file with some functions that depends on os and other stuff ? – wviana Nov 27 '17 at 19:22
0

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()
Benjamin Hodgson
  • 42,952
  • 15
  • 108
  • 157
  • Sorry for the late answer. The example above is simplified. I'm actually going to implement several math module and some of them on pure C. Of course it's possible by the use of classes as well, but I'm looking for an elegant and thin solution. As far as I understand, there's no one? May by I could use something like: import B import A A.configure(B)? – Ramil Nov 23 '15 at 01:46
  • That'd work, but the configuration would be _global_ - everyone that imports `A` would get the same implementation of `B`. That may be enough for you, but my advice would be to follow the lead of the standard library's `logging` or `asyncio` module - provide a flexible object model and build a simpler layer on top of that, using module-level configuration to set up the defaults. I'll update my answer. – Benjamin Hodgson Nov 23 '15 at 08:53