Tengo una clase Python 3 que actualmente es un singleton definido usando un decorador @singleton, pero ocasionalmente necesita no ser un singleton.

Pregunta: ¿Es posible hacer algo similar a pasar un parámetro al crear instancias de un objeto de la clase y este parámetro determina si la clase es singleton o no singleton?

Estoy tratando de encontrar una alternativa a la duplicación de la clase y hacer que no sea un singleton, pero luego tendremos toneladas de código duplicado.

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()
2
Nyxynyx 8 oct. 2019 a las 16:48

3 respuestas

La mejor respuesta

Puede agregar algo de manejo adicional en su contenedor singleton con un activador de palabra clave para omitir las instancias no individuales con singleton=False en su clase:

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}')

Prueba:

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

La advertencia es que querrás reservar una palabra clave que probablemente no se utilizará en ninguna de tus clases decoradas. El beneficio es que solo necesita crear la clase una vez sin duplicación.

1
r.ook 8 oct. 2019 a las 19:00

Creo que este problema se puede resolver fácilmente con la herencia. FooNotSingleton se convierte en una clase base con todos los detalles de implementación y Foo deriva de ella con el uso del decorador @singleton:

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()
0
sanyash 8 oct. 2019 a las 14:07

Puede compilar la clave de instancia en función de una ID única que pase al constructor. De esa manera, la misma clase y la misma ID producirá la misma instancia.

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()

Salida:

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

Opcionalmente: puede eliminar el argumento id de kwargs antes de pasarlo al constructor de la clase, y ofc podría nombrar id algo totalmente diferente.

0
Mike Scotty 8 oct. 2019 a las 14:12
58287723