2

I am middle of writing a singleton class for one of the requirement in python. I could realize that I am not able to restrict the object creation of that class, if some client makes the call first with constructor before using the singleton class method (means the method which return the only instance of the class).

What I mean by this ? Let me explain this with some code snippet. Consider I have one of this below sample :


import threading
class MyClass:

    __instance = None

    def __init__(self):

        if self.__instance:
            raise ValueError("Already exist")


    @classmethod
    def getInstance(cls):

        lock = threading.Lock()

        with lock:
            if cls.__instance == None:
                cls.__instance = MyClass()
            return cls.__instance

    def printObjInfo(self,obj):
        print(id(obj))


if __name__ == '__main__':

    ob4 = MyClass()
    ob4.printObjInfo(ob4)

    obj1 = MyClass.getInstance()
    obj1.printObjInfo(obj1)

    obj2 = MyClass.getInstance()
    obj2.printObjInfo(obj2)

    # Create a thread
    obj3 = MyClass.getInstance()
    obj3.printObjInfo(obj3)
    t1 = threading.Thread(target=obj3.printObjInfo, args=(obj3))

If I run this above code snippet, I would get a result as per below :

44824952 - Object created by constructor.
44826240 - Object created by getInstance() method.
44826240 - Object created by getInstance() method.
44826240 - Object created by getInstance() method.

One thing to note - if someone calls the constructor after calling getInstance() method then it's fine we can easily restrict the other object creation. But if it gets called first then we wouldn't be able to control that.

Now the problem is - 1) I can't put any further condition within __init__() to not anyone call this or 2) can't make my constructor private - can I ?

I found some reference in here - Program to restrict object creation in Python but not sure, how we can restrict the first object creation itself. Is there any better way which let's you do that ?

Any thoughts or references ?

Thank you.

2
  • There is nothing really private in python. So it is up to the user what to do with it Commented Apr 26, 2019 at 4:50
  • @Netwave - yes that what it seems like. Commented Apr 26, 2019 at 12:56

2 Answers 2

1

You can override __new__ instead:

class Singleton:

    _instance = None

    def __init__(self, arg):
        self.arg = arg

    def __new__(cls, arg):
        if cls._instance is None:
            cls._instance = object.__new__(cls)
            return cls._instance

        else:
            return cls._instance

print(Singleton(None))
print(Singleton(None))
print(Singleton(None))

Output:

<__main__.Singleton object at 0x1207fa550>
<__main__.Singleton object at 0x1207fa550>
<__main__.Singleton object at 0x1207fa550>

The advantage of doing this is that there is a single unified interface for creating and getting the single instance of a Singleton: the constructor.

Note that unless you write your own C extension or something similar, you will never be able to create a true singleton in Python:

print(object.__new__(Singleton))
print(object.__new__(Singleton))
print(object.__new__(Singleton))

Output:

<__main__.Singleton object at 0x120806ac8>
<__main__.Singleton object at 0x1207d5b38>
<__main__.Singleton object at 0x1207d5198>
Sign up to request clarification or add additional context in comments.

1 Comment

thanks, @gmds. Interesting code snippet. I would give it a try at my end.
0

A thread safe singleton version based on gmds's answer

from multiprocessing.dummy import Pool as ThreadPool 
import threading
import time

class ThreadSafeSingleton:
    __instance = None
    __lock = threading.Lock()

    def __init__(self, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs

    def __str__(self):
        return 'id: {}, args: {}, kwargs: {}'.format(id(self), self.args, self.kwargs)

    def __new__(cls, *args, **kwargs):
        with cls.__lock:
            if cls.__instance is None:
                # sleep just simulate heavy class initialization  !!
                # process under concurrency circumstance          !!
                time.sleep(1)
                print('created')
                cls.__instance = super(ThreadSafeSingleton, cls).__new__(cls)
            return cls.__instance


class ThreadUnsafeSingleton:
    __instance = None

    def __init__(self, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs

    def __str__(self):
        return 'id: {}, args: {}, kwargs: {}'.format(id(self), self.args, self.kwargs)

    def __new__(cls, *args, **kwargs):
        if cls.__instance is None:
            time.sleep(1)
            print('created')
            cls.__instance = super(ThreadUnsafeSingleton, cls).__new__(cls)
        return cls.__instance


def create_safe(*args, **kwargs):
    obj = ThreadSafeSingleton(*args, **kwargs)
    print(obj)


def create_unsafe(*args, **kwargs):
    obj = ThreadUnsafeSingleton(*args, **kwargs)
    print(obj)


if __name__ == '__main__':
    pool = ThreadPool(4)
    print('---- test thread safe singleton  ----')
    pool.map(create_safe, range(10))

    print('\n---- test thread unsafe singleton  ----')
    pool.map(create_unsafe, range(10))

Output:

---- test thread safe singleton  ----
created
id: 4473136352, args: (0,), kwargs: {}
id: 4473136352, args: (1,), kwargs: {}
id: 4473136352, args: (2,), kwargs: {}
id: 4473136352, args: (4,), kwargs: {}
id: 4473136352, args: (5,), kwargs: {}
id: 4473136352, args: (3,), kwargs: {}
id: 4473136352, args: (6,), kwargs: {}
id: 4473136352, args: (7,), kwargs: {}
id: 4473136352, args: (8,), kwargs: {}
id: 4473136352, args: (9,), kwargs: {}

---- test thread unsafe singleton  ----
created
id: 4473136968, args: (0,), kwargs: {}
created
created
created
id: 4473136968, args: (4,), kwargs: {}
id: 4473137024, args: (2,), kwargs: {}
id: 4473137080, args: (1,), kwargs: {}
id: 4473137136, args: (3,), kwargs: {}
id: 4473137136, args: (5,), kwargs: {}
id: 4473137136, args: (7,), kwargs: {}
id: 4473137136, args: (6,), kwargs: {}
id: 4473137136, args: (8,), kwargs: {}
id: 4473137136, args: (9,), kwargs: {}

Comments

Your Answer

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.