Ray Ray - 2 months ago 16
Python Question

Use module as class instance in Python

TL; DR



Basically the question is about hiding from the user the fact that my modules have class implementations so that the user can use the module as if it has direct function definitions like
my_module.func()


Details



Suppose I have a module
my_module
and a class
MyThing
that lives in it. For example:

# my_module.py

class MyThing(object):
def say():
print("Hello!")


In another module, I might do something like this:

# another_module.py

from my_module import MyThing

thing = MyThing()
thing.say()


But suppose that I don't want to do all that. What I really want is for
my_module
to create an instance of MyThing automatically on
import
such that I can just do something like the following:

# yet_another_module.py

import my_module

my_module.say()


In other words, whatever method I call on the module, I want it to be forwarded directly to a default instance of the class contained in it. So, to the user of the module, it might seem that there is no class in it, just direct function definitions in the module itself (where the functions are actually methods of a class contained therein). Does that make sense? Is there a short way of doing this?

I know I could do the following in
my_module
:

class MyThing(object):
def say():
print("Hello!")

default_thing = MyThing()

def say():
default_thing.say()


But then suppose
MyThing
has many "public" methods that I want to use, then I'd have to explicitly define a "forwarding" function for every method, which I don't want to do.

As an extension to my question above, is there a way to achieve what I want above, but also be able to use code like
from my_module import *
and be able to use methods of
MyThing
directly in another module, like
say()
?

Answer

In module my_module do the following:

class MyThing(object):
    ...

_inst = MyThing()
say = _inst.say
move = _inst.move

This is exactly the pattern used by the random module.

Doing this automatically is somewhat contrived. First, one needs to find out which of the instance/class attributes are the methods to export... perhaps export only names which do not start with _, something like

import inspect
for name, member in inspect.getmembers(Foo(), inspect.ismethod):
    if not name.startswith('_'):
        globals()[name] = member

However in this case I'd say that explicit is better than implicit.

Comments