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()
sanyassh
  • 8,100
  • 13
  • 36
  • 70
Nyxynyx
  • 61,411
  • 155
  • 482
  • 830
  • 1. `_singleton` is not defined. Did you mean `getinstance`?. 2. Silly question: why not just remove `@singleton` decorator? – sanyassh Oct 08 '19 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? – Nyxynyx Oct 08 '19 at 13:56
  • would you mind accepting one of given answers? – sanyassh Oct 25 '19 at 15:38

3 Answers3

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.

r.ook
  • 13,466
  • 2
  • 22
  • 39
  • 1
    `singleton = kwargs.pop('singleton', True)` can replace `try: ... except` block. – sanyassh Oct 08 '19 at 18:50
  • Good point, wasn't aware `pop()` also has a default that won't trigger the `KeyError`. Good to know! – r.ook Oct 08 '19 at 18:59
  • 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. – Kunal Joshi Jun 01 '22 at 12:57
  • @r.ook Need your help here please https://stackoverflow.com/questions/72456961/python-class-singleton-pass-the-multiple-argument-to-check-comparison – Kunal Joshi Jun 01 '22 at 14:36
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.

Mike Scotty
  • 10,530
  • 5
  • 38
  • 50
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()
sanyassh
  • 8,100
  • 13
  • 36
  • 70