2

I have a Python 3 class that is currently a singleton defined using a @singleton decorator, but occasionally it needs to not be a singleton.

Question: Is it possible to do something similar to passing a parameter when instantiating an object from the class and this parameter determines whether the class is a singleton or not a singleton?

I am trying to find an alternative to duplicating the class and making that not a singleton, but then we will have tons of duplicated code.

Foo.py

def singleton(cls):
    instances={}

    def getinstance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]

    return getinstance

@singleton
Class Foo:
    def hello(self):
        print('hello world!')

FooNotSingleton.py

Class FooNotSingleton:
    def hello(self):
        print('hello world!')

main.py

from Foo import Foo
from FooNotSingleton import FooNotSingleton

foo = Foo()
foo.hello()

bar = FooNotSingleton()
bar.hello()
3
  • 1. _singleton is not defined. Did you mean getinstance?. 2. Silly question: why not just remove @singleton decorator? Commented Oct 8, 2019 at 13:54
  • @sanyash 1. Fixed the typo, thanks! 2. I will like to have a singleton and a non-singleton version of the same class, so the singleton version of the class should have the @singleton decorator, the non-singleton version should not. Maybe I am missing out something really obvious? Commented Oct 8, 2019 at 13:56
  • would you mind accepting one of given answers? Commented Oct 25, 2019 at 15:38

3 Answers 3

1

You can add some extra handling in your singleton wrapper with a keyword trigger to bypass the non-single instantiations with singleton=False in your class:

def singleton(cls):
    instances={}

    def getinstance(*args, **kwargs):
        # thanks to sanyash's suggestion, using a default return instead of try/except            
        singleton = kwargs.pop('singleton', True)
        if singleton:
            if cls not in instances:
                instances[cls] = cls(*args, **kwargs)
            return instances[cls]
        else:
            return cls(*args, **kwargs)

    return getinstance

@singleton
class Foo:
    def __init__(self, val):
        self.val = val
    def hello(self):
        print(f'I have value {self.val}')

Test:

s1 = Foo('single')
s2 = Foo('another single')
ns = Foo('not single', singleton=False)
s1.hello()
# I have value single
s2.hello()
# I have value single
ns.hello()
# I have value not single

The caveat is you'll want to reserve a keyword that aren't likely to be used to be in any of your decorated class. The benefit is you only need to create the class once without duplication.

Sign up to request clarification or add additional context in comments.

4 Comments

singleton = kwargs.pop('singleton', True) can replace try: ... except block.
Good point, wasn't aware pop() also has a default that won't trigger the KeyError. Good to know!
a1 = Foo('apple', category='fruits') a2 = Foo('apple', category='fruits', origin='USA') b = Foo('grape', category='fruits') print(a1 is a1) print(a1 is a2) print(a1 is b) How to pass this arguments and check comparison. If true or false.
@r.ook Need your help here please stackoverflow.com/questions/72456961/…
0

I believe this issue can easily be solved with inheritance. FooNotSingleton becomes a base class with all implementation details and Foo derives from it with usage of @singleton decorator:

FooNotSingleton.py

class FooNotSingleton:
    def hello(self):
        print('hello world!')

Foo.py

import FooNotSingleton

def singleton(cls):
    instances={}

    def getinstance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]

    return getinstance

@singleton
class Foo(FooNotSingleton.FooNotSingleton):
    pass

main.py

from Foo import Foo
from FooNotSingleton import FooNotSingleton

print(id(FooNotSingleton()))
print(id(FooNotSingleton()))  # different
print(id(Foo()))
print(id(Foo()))  # same
FooNotSingleton().hello()  # both works
Foo().hello()

Comments

0

You could build the instance key based on a unique ID that you pass to the constructor. That way, the same class and the same ID will yield the same instance.

def singleton(cls):
    instances={}
    def getinstance(*args, **kwargs):
        key = "{}__{}".format(cls, kwargs.get("id"))
        if key not in instances:
            instances[key] = cls(*args, **kwargs)
        return instances[key]
    return getinstance

@singleton
class Foo:
    def __init__(self, *args, **kwargs):
        self.x = 0
    def hello(self):
        print('My X is:', self.x)

f1 = Foo()
f1.x = 5
f1.hello()

f2 = Foo() # same as f1
f2.hello()

f3 = Foo(id='abc') # new instance, because of new "id" parameter
f3.x = 1024
f3.hello()

f4 = Foo() # same as f1
f4.hello()

Output:

My X is: 5
My X is: 5
My X is: 1024
My X is: 5

Optionally: You could remove the id argument from kwargs before you pass it to the class constructor - and ofc you could name id something totally different.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.