13

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()?

asked Sep 12, 2016 at 10:00
3
  • 3
    You can but why would you want to really? The way you'd normally do such a thing is from something import something, then just use something.(...) where something is your class instance... Commented Sep 12, 2016 at 10:04
  • You can use MyThing().say() Commented Sep 12, 2016 at 10:07
  • 2
    @JonClements I suppose you're right, but the reason I want to do this is that I'm refactoring some code into classes, and loads of other code in the code base that use this module already use it as I've described in the question, so I don't want everyone to change the way they use the module due to my refactoring of the module code into classes. (And the reason I want to refactor the module code into classes is because currently there is a lot of code smell and I could use class inheritance to reuse bits of code.) Commented Sep 12, 2016 at 10:16

3 Answers 3

14

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.

answered Sep 12, 2016 at 10:15
Sign up to request clarification or add additional context in comments.

5 Comments

This would do exactly what I want to do. The only thing is that I'd have to forward every function (like say and move) to their corresponding methods (like _instance.say and _instance.move). Consequently, if I (or somebody else) want to add public methods to MyThing, they'd have to remember to do the forwarding as well. This is fine if it's the only way, but is it there only way? There is no way to forward all methods in one go, so that I (or anybody else) don't have to remember to explicitly do the forwarding for every method they might add to MyThing?
yes, there is, but the question why continue doing it, as it would contribute to code smell
Ah, I just read the bit where you pointed out that this is what random does. It somehow puts myself at ease that one of the standard modules does this sort of thing. Still, I'm academically curious as to how to forward all methods in one go. Also, I can't imagine at the moment how it would contribute to code smell; I can only imagine that it'll get rid of code smell, but that's because I lack experience.
I think @SteveJessop answer contains a bit of explanation as to why I wouldn't want to forward all methods in one go. I think I'll stick to the explicit forwarding pattern. Thank you for your answer.
@Ray added one possible solution
2

You could just replace:

def say():
 return default_thing.say()

with:

say = default_thing.say

You still have to list everything that's forwarded, but the boilerplate is fairly concise.

If you want to replace that boilerplate with something more automatic, note that (details depending on Python version), MyThing.__dict__.keys() is something along the lines of ['__dict__', '__weakref__', '__module__', 'say', '__doc__']. So in principle you could iterate over that, skip the __ Python internals, and call setattr on the current module (which is available as sys.modules[__name__]). You might later regret not listing this stuff explicitly in the code, but you could certainly do it.

Alternatively you could get rid of the class entirely as use the module as the unit of encapsulation. Wherever there is data on the object, replace it with global variables. "But", you might say, "I've been warned against using global variables because supposedly they cause problems". The bad news is that you've already created a global variable, default_thing, so the ship has sailed on that one. The even worse news is that if there is any data on the object, then the whole concept of what you want to do: module-level functions that mutate a shared global state, carries with it most of the problems of globals.

answered Sep 12, 2016 at 10:20

3 Comments

The main reason I don't want to use modules as the unit of encapsulation is that I want to take advantage of class inheritance. For example, in fact I have two modules and I need one of them to inherit a lot of functions from the other one, but not the global variables. The best way to do this (that I can imagine) is to make them into classes and use class inheritance. Right now I'm wondering if there is a wholesale way of forwarding all the methods in one go. I'm instinctively imagining something like def __getattr__(attr): return default_thing.__getattr__(attr).
@Ray: to overload __getattr__ on a module I think you need to define the type of the module to your own custom type. Just defining __getattr__ won't work, I think, because of the way builtin __blah__ hooks are looked up. I'm not sure that's possible, but I suppose you might as well try __getattr__, if that doesn't work look into ways to specify the that a module has custom type, and if you get something working answer your own question ;-)
Thank you very much for your extra explanations and insights! By your explanations I understand now that the easier way is just to forward all public methods explicitly. I wish I could upvote you more than once.
0

Not Sure why this wouldn't work.

say = MyClass().say()
from my_module import *
say
>>Hello!
answered Sep 12, 2016 at 10:10

1 Comment

Not quite. If I were to do from my_module import * then I'd want to be able to do say() for the output Hello!.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.