问题
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()
回答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.
回答2:
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()
回答3:
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.
来源:https://stackoverflow.com/questions/58287723/python-class-singleton-or-not-singleton-by-passing-a-value