1

I would like to convert a singleton-object programmatically into a Python module so that I can use the methods of this singleton-object directly by importing them via the module instead of accessing them as object attributes. By "programmatically" I mean that I do not want to have to copy-paste the class methods explicitly into a module file. I need some sort of a workaround that allows me to import the object methods into to global scope of another module.

I would really appreciate if someone could help me on this one.

Here is a basic example that should illustrate my problem:

mymodule.py

class MyClass:
"""This is my custom class"""
    def my_method(self):
        return "myValue"

singleton = MyClass()

main_as_is.py

from mymodule import MyClass

myobject = MyClass()
print(myobject.my_method())

main_to_be.py

from mymodule import my_method # or from mymodule.singleton import my_method

print(my_method())
PM 2Ring
  • 54,345
  • 6
  • 82
  • 182
Jerry
  • 23
  • 5
  • Give us a stripped-down example of two dozen lines or so that we know exactly what you are trying to achieve. – BoarGules May 16 '18 at 12:20
  • 5
    Where's the benefit of doing this? Instead of writing `someobj.thing` you'd write `somemodule.thing`. Unless of course you intend to use a "star" import equivalent, but that's generally not a Good Idea. See https://stackoverflow.com/questions/2386714/why-is-import-bad and https://pythonconquerstheuniverse.wordpress.com/2011/03/28/why-import-star-is-a-bad-idea/ – PM 2Ring May 16 '18 at 12:21
  • @PM2Ring: My boss prefers procedural style programming so he asked me to do this. In fact he would like to import the methods explicitly like "from mymodule import method1, method2" ... The benefit would be that you don't have to initialize an object and that you don't have to type "someobj" each time you use a method ;) – Jerry May 17 '18 at 08:29
  • 3
    You might need a new boss :P – Wayne Werner May 18 '18 at 19:30
  • 2
    What does your boss mean by "procedural style programming"? If he's opposed to OOP, I doubt he'd be very happy with a Java-style Singleton Design Pattern, which is the exact kind of thing most anti-OOP people hate. Or, if he means "not functional like Scheme" (or Haskell), using the class instead of a closure is just going to look like pointless obfuscation to him. – abarnert May 20 '18 at 05:24
  • By "procedural style programming" I (and not my Boss) mean that he wants to use procedures instead of object methods. https://en.wikipedia.org/wiki/Procedural_programming – Jerry May 22 '18 at 06:17

1 Answers1

3

You can use the same strategy that the standard random module uses. All the functions in that module are actually methods of a "private" instance of the Random class. That's convenient for most common uses of the module, although sometimes it's useful to create your own instances of Random so that you can have multiple independent random streams.

I've adapted your code to illustrate that technique. I named the class and its instance with a single leading underscore, since that's the usual convention in Python to signify a private name, but bear in mind it's simply a convention, Python doesn't do anything to enforce this privacy.

mymodule.py

class _MyClass:
    """ This is my custom class """
    def my_method(self):
        return "myValue"

_myclass = _MyClass()
my_method = _myclass.my_method

main_to_be.py

from mymodule import my_method

print(my_method())       

output

myValue

BTW, the from mymodule import method1, method2 syntax is ok if you only import a small number of names, or it's clear from the name which module it's from (like math module functions and constants), and you don't import from many modules. Otherwise it's better to use this sort of syntax

import mymodule as mm
# Call a method from the module
mm.method1()

That way it's obvious which names are local, and which ones are imported and where they're imported from. Sure, it's a little more typing, but it makes the code a whole lot more readable. And it eliminates the possibility of name collisions.

FWIW, here's a way to automate adding all of the _myclass methods without explicitly listing them (but remember "explicit is better than implicit"). At the end of "mymodule.py", in place of my_method = _myclass.my_method, add this:

globals().update({k: getattr(_myclass, k) for k in _MyClass.__dict__ 
    if not k.startswith('__')})

I'm not comfortable with recommending this, since it directly injects items into the globals() dict. Note that that code will add all class attributes, not just methods.


In your question you talk about singleton objects. We don't normally use singletons in Python, and many programmers in various OOP languages consider them to be an anti-pattern. See https://stackoverflow.com/questions/12755539/why-is-singleton-considered-an-anti-pattern for details. For this application there is absolutely no need at all to use a singleton. If you only want a single instance of _MyClass then simply don't create another instance of it, just use the instance that mymodule creates for you. But if your boss insists that you must use a singleton, please see the example code here.

PM 2Ring
  • 54,345
  • 6
  • 82
  • 182
  • Many thanks. The problem with this approach is that I have to assign all the methods from the class one-by-one to some local variable and thus I would still need to copy paste everything. I would like to avoid code duplication. – Jerry May 22 '18 at 06:36
  • @Jerry Do you mean you want to avoid in "mymodule.py" having to do `my_method = _myclass.my_method` for each method of `_myclass`? – PM 2Ring May 22 '18 at 06:39
  • @Jerry If so, there's a way to do that, but it's a bit ugly. FWIW, the random module does explicit one-by-one assignment, see the end of the module's [source code](https://github.com/python/cpython/blob/3.6/Lib/random.py). – PM 2Ring May 22 '18 at 07:04
  • I agree with the fact that there is not much to gain here and that things should not be overcomplicated with some controversial Design Pattern ;) I think though that there should be a simple way to include some functionalities in an exclusively procedural module or package that were initially implemented in a OOP style without forcing the use of OOP concepts. – Jerry May 22 '18 at 07:16
  • I guess I should not have used the term "singleton" in my problem description. In the end the only thing that this approach has in common with the Singleton Pattern is that it uses a single instance of a class to serve a specific purpose. The key difference is that this instance would not be accessed explicitly throughout the project (which I guess is what makes the Singleton Pattern a Design Pattern and which makes it prone to misuse) but it would be encapsulated inside a module. – Jerry May 22 '18 at 07:17
  • Like with the random module, the idea here is not to prevent the usage of multiple instances of the class in question. The idea here is to provide some sort of a shortcut for heavy function usage ;) – Jerry May 22 '18 at 07:18
  • Yes exactly, I would like to avoid that since this class has a huge amount of methods ;) And it will be extended in the future. I saw your previous proposal with the metaclass, I think this might solve the problem. I will let you know when I tested it. Thanks – Jerry May 22 '18 at 07:22
  • @Jerry FWIW, here's a way to automate adding all of the `_myclass` methods without explicitly listing them (but remember "explicit is better than implicit"). At the end of "mymodule.py", in place of `my_method = _myclass.my_method`, add this: `globals().update({k: getattr(_myclass, k) for k in _MyClass.__dict__ if not k.startswith('__')})` – PM 2Ring May 22 '18 at 07:26
  • @Jerry But really, as I said in my answer, the `from mymodule import method1, method2` syntax isn't a good idea if you're importing lots of methods, since it makes the code harder to read. – PM 2Ring May 22 '18 at 07:29
  • First of all I would like to thank you, especially for your second last comment, which is actually the solution I was looking for. Since I am new to Python I was not aware of the globals() method. I agree with you. It is generally not a good idea, but in the special case where you use only a few methods extensively it can be helpful though. I guess it is not possible to mark a comment as "accepted answer"? – Jerry May 22 '18 at 11:53