Crear un singleton en Python


946

Esta pregunta no es para la discusión de si el patrón de diseño único o no es deseable, es un antipatrón o para cualquier guerra religiosa, sino para discutir cómo este patrón se implementa mejor en Python de la manera más pitónica. En este caso, defino 'más pitónico' para que signifique que sigue el 'principio del menor asombro' .

Tengo varias clases que se convertirían en singletons (mi caso de uso es para un registrador, pero esto no es importante). No deseo saturar varias clases con gumph añadido cuando simplemente puedo heredar o decorar.

Mejores métodos:


Método 1: un decorador

def singleton(class_):
    instances = {}
    def getinstance(*args, **kwargs):
        if class_ not in instances:
            instances[class_] = class_(*args, **kwargs)
        return instances[class_]
    return getinstance

@singleton
class MyClass(BaseClass):
    pass

Pros

  • Los decoradores son aditivos de una manera que a menudo es más intuitiva que la herencia múltiple.

Contras

  • Si bien los objetos creados con MyClass () serían verdaderos objetos singleton, MyClass en sí es una función, no una clase, por lo que no puede llamar a los métodos de clase desde ella. También para m = MyClass(); n = MyClass(); o = type(n)();entoncesm == n && m != o && n != o

Método 2: una clase base

class Singleton(object):
    _instance = None
    def __new__(class_, *args, **kwargs):
        if not isinstance(class_._instance, class_):
            class_._instance = object.__new__(class_, *args, **kwargs)
        return class_._instance

class MyClass(Singleton, BaseClass):
    pass

Pros

  • Es una verdadera clase

Contras

  • Herencia múltiple - ¡eugh! __new__podría sobrescribirse durante la herencia de una segunda clase base? Uno tiene que pensar más de lo necesario.

Método 3: una metaclase

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

#Python2
class MyClass(BaseClass):
    __metaclass__ = Singleton

#Python3
class MyClass(BaseClass, metaclass=Singleton):
    pass

Pros

  • Es una verdadera clase
  • Auto-mágicamente cubre la herencia
  • Usos __metaclass__para su propósito apropiado (y me hizo consciente de ello)

Contras

  • ¿Hay alguna?

Método 4: decorador que devuelve una clase con el mismo nombre

def singleton(class_):
    class class_w(class_):
        _instance = None
        def __new__(class_, *args, **kwargs):
            if class_w._instance is None:
                class_w._instance = super(class_w,
                                    class_).__new__(class_,
                                                    *args,
                                                    **kwargs)
                class_w._instance._sealed = False
            return class_w._instance
        def __init__(self, *args, **kwargs):
            if self._sealed:
                return
            super(class_w, self).__init__(*args, **kwargs)
            self._sealed = True
    class_w.__name__ = class_.__name__
    return class_w

@singleton
class MyClass(BaseClass):
    pass

Pros

  • Es una verdadera clase
  • Auto-mágicamente cubre la herencia

Contras

  • ¿No hay una sobrecarga para crear cada nueva clase? Aquí estamos creando dos clases para cada clase que deseamos hacer un singleton. Si bien esto está bien en mi caso, me preocupa que esto no pueda escalar. Por supuesto, hay una cuestión de debate sobre si sería demasiado fácil escalar este patrón ...
  • ¿Cuál es el punto del _sealedatributo?
  • No se pueden llamar métodos del mismo nombre en clases base usando super()porque recurrirán . Esto significa que no puede personalizar __new__y no puede subclasificar una clase que necesita llamar __init__.

Método 5: un módulo

un archivo de módulo singleton.py

Pros

  • Simple es mejor que complejo

Contras


12
Otras tres técnicas: use un módulo en su lugar (a menudo, en general, creo, este es un patrón más apropiado para Python, pero depende un poco de lo que esté haciendo con él); crea una sola instancia y trata con ella en su lugar ( foo.xo si insistes en Foo.xlugar de Foo().x); use atributos de clase y métodos estáticos / de clase ( Foo.x).
Chris Morgan

10
@ChrisMorgan: Si vas a usar solo métodos de clase / estáticos, entonces no te molestes en hacer una clase, de verdad.
Cat Plus Plus el

2
@ Cat: El efecto es similar, sin embargo, las razones detrás de la creación de una variable global pueden ser casi cualquier cosa, incluso no saber nada mejor. ¿Por qué uno crea un singleton? Si tienes que preguntar, no deberías estar aquí. Esta explicidad no solo es más pitónica, sino que hace que el mantenimiento sea mucho más simple. Sí, los singletons son azúcar sintáctica para los globales, pero luego las clases son azúcar sintáctica para un montón de cosas desagradables y no creo que nadie te diga que siempre estás mejor sin ellas.
theheadofabroom

14
@BiggAl: los Singletons no son Pythonic, no importa cómo los implemente. Son un signo de un diseño defectuoso en el mejor de los casos.
Cat Plus Plus el

8
El sentimiento anti-esqueletos es la programación de culto de carga en su peor momento. Lo mismo ocurre con las personas que oyen (pocos se molestaron en leer realmente) "La declaración Goto se considera dañina" y piensa que los gotos son un signo de código incorrecto, independientemente del contexto.
Hejazzman

Respuestas:


658

Use una metaclase

Recomendaría el Método # 2 , pero es mejor usar una metaclase que una clase base. Aquí hay una implementación de muestra:

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class Logger(object):
    __metaclass__ = Singleton

O en Python3

class Logger(metaclass=Singleton):
    pass

Si desea ejecutar __init__cada vez que se llama a la clase, agregue

        else:
            cls._instances[cls].__init__(*args, **kwargs)

a la ifdeclaración enSingleton.__call__ .

Algunas palabras sobre metaclases. Una metaclase es la clase de una clase ; es decir, una clase es una instancia de su metaclase . Encuentra la metaclase de un objeto en Python con type(obj). Las clases normales de nuevo estilo son de tipo type. Loggeren el código anterior será de tipo class 'your_module.Singleton', así como la (única) instancia de Loggerserá de tipo class 'your_module.Logger'. Cuando se llama registrador con Logger(), Python primera pregunta al metaclase de Logger, Singleton, qué hacer, lo que permite la creación de instancias que se ha vaciado anteriormente. Este proceso es el mismo que Python pregunta a una clase qué hacer llamando __getattr__al hacer referencia a uno de sus atributos haciendo myclass.attribute.

Una metaclase esencialmente decide qué significa la definición de una clase y cómo implementar esa definición. Consulte, por ejemplo , http://code.activestate.com/recipes/498149/ , que esencialmente recrea los C-style structen Python usando metaclases. El hilo ¿Cuáles son algunos casos de uso (concretos) para metaclases?también proporciona algunos ejemplos, generalmente parecen estar relacionados con la programación declarativa, especialmente como se usa en ORM.

En esta situación, si usa su Método # 2 , y una subclase define un __new__método, se ejecutará cada vez que llame SubClassOfSingleton(), porque es responsable de llamar al método que devuelve la instancia almacenada. Con una metaclase, solo se llamará una vez , cuando se cree la única instancia. Desea personalizar lo que significa llamar a la clase , que se decide por su tipo.

En general, tiene sentido usar una metaclase para implementar un singleton. Un singleton es especial porque se crea solo una vez , y una metaclase es la forma en que personaliza la creación de una clase . El uso de una metaclase le brinda más control en caso de que necesite personalizar las definiciones de clase singleton de otras maneras.

Sus singletons no necesitarán herencia múltiple (porque la metaclase no es una clase base), pero para las subclases de la clase creada que usan herencia múltiple, debe asegurarse de que la clase singleton sea la primera / izquierda con una metaclase que redefina __call__Es muy poco probable que esto sea un problema. La instancia dict no está en el espacio de nombres de la instancia, por lo que no la sobrescribirá accidentalmente.

También escuchará que el patrón singleton viola el "Principio de responsabilidad única": cada clase debe hacer solo una cosa . De esa manera, no tiene que preocuparse por estropear una cosa que hace el código si necesita cambiar otra, porque están separadas y encapsuladas. La implementación de metaclase pasa esta prueba . La metaclase es responsable de aplicar el patrón y la clase y las subclases creadas no necesitan ser conscientes de que son singletons . El método # 1 falla esta prueba, como notó con "MyClass en sí es una función, no una clase, por lo que no puede llamar a los métodos de clase desde ella".

Versión compatible con Python 2 y 3

Escribir algo que funcione tanto en Python2 como en 3 requiere usar un esquema un poco más complicado. Desde metaclases suelen ser subclases de tipo type, es posible usar uno para crear dinámicamente una clase base intermediario en tiempo de ejecución con él como su metaclase y luego usar que como los baseclass del público Singletonde la clase base. Es más difícil de explicar que de hacer, como se ilustra a continuación:

# works in Python 2 & 3
class _Singleton(type):
    """ A metaclass that creates a Singleton base class when called. """
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(_Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class Singleton(_Singleton('SingletonMeta', (object,), {})): pass

class Logger(Singleton):
    pass

Un aspecto irónico de este enfoque es que utiliza subclases para implementar una metaclase. Una posible ventaja es que, a diferencia de una metaclase pura, isinstance(inst, Singleton)volverá True.

Correcciones

En otro tema, probablemente ya lo haya notado, pero la implementación de la clase base en su publicación original es incorrecta. _instancesnecesita ser referenciado en la clase , debe usar super()o está recurriendo , y en __new__realidad es un método estático al que debe pasar la clase , no un método de clase, ya que la clase real aún no se ha creado cuando se llama. Todas estas cosas serán ciertas para una implementación de metaclase también.

class Singleton(object):
  _instances = {}
  def __new__(class_, *args, **kwargs):
    if class_ not in class_._instances:
        class_._instances[class_] = super(Singleton, class_).__new__(class_, *args, **kwargs)
    return class_._instances[class_]

class MyClass(Singleton):
  pass

c = MyClass()

Decorador devolviendo una clase

Originalmente estaba escribiendo un comentario pero era demasiado largo, así que agregaré esto aquí. El Método # 4 es mejor que la otra versión del decorador, pero es más código del necesario para un singleton, y no está tan claro lo que hace.

Los principales problemas se derivan de que la clase es su propia clase base. Primero, ¿no es extraño que una clase sea una subclase de una clase casi idéntica con el mismo nombre que existe solo en su __class__atributo? Esto también significa que no se puede definir cualquier método que llaman al método del mismo nombre en la clase base con super()porque van a recursiva. Esto significa que su clase no puede personalizarse __new__, y no puede derivarse de ninguna clase que necesite __init__invocarlos.

Cuándo usar el patrón singleton

Su caso de uso es uno de los mejores ejemplos de querer usar un singleton. Usted dice en uno de los comentarios "Para mí, el registro siempre me ha parecido un candidato natural para Singletons". Tienes toda la razón .

Cuando las personas dicen que los singletons son malos, la razón más común es que son estados compartidos implícitos . Mientras que con las variables globales y las importaciones de módulos de nivel superior son un estado compartido explícito , otros objetos que se pasan generalmente se instancian. Este es un buen punto, con dos excepciones .

El primero, y uno que se menciona en varios lugares, es cuando los singletons son constantes . El uso de constantes globales, especialmente enumeraciones, es ampliamente aceptado y considerado sensato porque no importa qué, ninguno de los usuarios puede confundirlos con ningún otro usuario . Esto es igualmente cierto para un singleton constante.

La segunda excepción, que se menciona menos, es lo contrario: cuando el singleton es solo un sumidero de datos , no una fuente de datos (directa o indirectamente). Esta es la razón por la cual los registradores se sienten como un uso "natural" para los singletons. Como los diversos usuarios no están cambiando los registradores de la forma en que otros usuarios se preocuparán, en realidad no hay un estado compartido . Esto niega el argumento principal contra el patrón singleton y los convierte en una opción razonable debido a su facilidad de uso para la tarea.

Aquí hay una cita de http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html :

Ahora, hay un tipo de Singleton que está bien. Ese es un singleton donde todos los objetos accesibles son inmutables. Si todos los objetos son inmutables, Singleton no tiene un estado global, ya que todo es constante. Pero es tan fácil convertir este tipo de singleton en mutable, es una pendiente muy resbaladiza. Por lo tanto, también estoy en contra de estos Singletons, no porque sean malos, sino porque es muy fácil que se vuelvan malos. (Como nota al margen, la enumeración de Java es solo este tipo de singletons. Siempre que no ponga el estado en su enumeración, está bien, así que no lo haga).

El otro tipo de Singletons, que son semi-aceptables, son aquellos que no afectan la ejecución de su código, no tienen "efectos secundarios". El registro es un ejemplo perfecto. Está cargado con Singletons y estado global. Es aceptable (ya que no le hará daño) porque su aplicación no se comporta de manera diferente si un registrador determinado está habilitado o no. La información aquí fluye de una manera: desde su aplicación al registrador. Incluso si los registradores son de estado global, ya que no fluye información de los registradores a su aplicación, los registradores son aceptables. Todavía debe inyectar su registrador si desea que su prueba afirme que algo se está registrando, pero en general los registradores no son dañinos a pesar de estar llenos de estado.


55
No, los singletons nunca son buenos. El registro podría ser un buen candidato para ser global (por terribles que sean), pero ciertamente no es un singleton.
Cat Plus Plus el

11
Mira googletesting.blogspot.com/2008/08/… . En general, es anti-singleton (por una buena razón), pero tiene una buena explicación de por qué los singleton inmutables y los singleton sin efectos secundarios no tienen los mismos problemas, si tienes cuidado. Voy a citarlo un poco al final de mi publicación.
agf

44
Mi problema con los singletons es la estúpida premisa de "solo una instancia". Eso y toneladas de problemas de seguridad del hilo. Y la ocultación de la dependencia. Los globales son malos, y los singletons son solo globales con más problemas.
Cat Plus Plus

44
@ Cat Hay muy buenos usos para los singletons. La creación de instancias perezosas de módulos de hardware (especialmente en aplicaciones de un solo subproceso) es uno de ellos (pero también existen singletons seguros para subprocesos).
Paul Manta

3
@Alcott __new__en una metaclase es cuando la clase es nueva, cuando está definida, no cuando la instancia sería nueva. Llamar a la clase ( MyClass()) es la operación que desea anular, no la definición de la clase. Si realmente quiere entender cómo funciona Python, lo mejor que puede hacer (aparte de seguir usándolo) es leer docs.python.org/reference/datamodel.html . Una buena referencia en metaclases es eli.thegreenplace.net/2011/08/14/python-metaclasses-by-example . Un buen artículo sobre singletons es la serie del blog de google que vinculé en esta respuesta.
agf

91
class Foo(object):
     pass

some_global_variable = Foo()

Los módulos se importan solo una vez, todo lo demás está pensando demasiado. No use singletons e intente no usar globals.


14
¿Por qué dijiste "No uses singletons"? ¿Cualquier razón?
Alcott

3
Esto no funcionará si el singleton tiene que ser conservado en vinagre. Usando el ejemplo que diste:s = some_global_variable; str = pickle.dumps(s); s1 = pickle.loads(str); print s is s1; # False
DivideByZero

44
@dividebyzero: el isoperador prueba la igualdad del puntero. Me sorprendería bastante --- hasta el punto de llamarlo un error --- si pickle.loadsdevolviera una referencia a un objeto preexistente en lugar de una referencia a uno recién creado. Por lo tanto, probar si s is s1no le dice nada sobre la idoneidad del uso de módulos como singletons.
Jonas Kölker

1
@ JonasKölker pickle.loads()ya lo hace, por ejemplo, para instancias de booly NoneType. pickle.loads(pickle.dumps(False)) is FalserendimientosTrue
Dan Passaro

2
@ leo-the-manic: punto justo; Sin embargo, eso es sólo un efecto secundario de Python internar los objetos True, Falsey None, y no tiene nada que ver con el código detrás pickle.loads. Además, es seguro hacerlo solo para objetos de solo lectura. Si pickle.loadsdevolviera una referencia a un objeto modificable ya existente , como un módulo, eso sería un error. (Y por lo tanto, estoy de acuerdo con mi implicación de que el ejemplo de código de dividebyzero no prueba nada).
Jonas Kölker

69

Usa un módulo. Se importa solo una vez. Defina algunas variables globales en él: serán los 'atributos' de singleton. Agregue algunas funciones: los 'métodos' del singleton.


11
Entonces con lo que terminas es ... No es una clase. No se puede utilizar como una clase, no se puede base de otras clases sobre ella, que utilizar la sintaxis de importación, y, de repente, se pierden todos los beneficios de la programación orientada a objetos ...
theheadofabroom

16
si puede basar otras clases en él, entonces podría no ser un singleton. podría crear una de la clase derivada, pero también una de la clase base, pero la clase derivada también es miembro de la base, y tiene dos de la base, ¿cuál se supone que debe usar?
SingleNegationElimination

Esto no funciona en todos los módulos. En mi módulo "principal" establecí un valor. Luego lo hago referencia en otro módulo y es nulo. Suspiro.
Paul Kenjora

1
@PaulKenjora Debe tener un error en su código. Si define una variable global en un módulo, cuando acceda a ella desde otro módulo, debería tener el valor.
warvariuc

Tiene el valor pero cuando lo cambio no se conserva. Terminé usando una clase con propiedades en un módulo que funciona. Los tipos simples como globales no funcionaron para mí (perdieron valores tan pronto como cambió el alcance).
Paul Kenjora el

29

Probablemente nunca necesite un singleton en Python. Simplemente defina todos sus datos y funciones en un módulo y tendrá un singleton de facto.

Si realmente tienes que tener una clase de singleton, entonces iría con:

class My_Singleton(object):
    def foo(self):
        pass

my_singleton = My_Singleton()

Usar:

from mysingleton import my_singleton
my_singleton.foo()

donde mysingleton.py es su nombre de archivo en el que se define My_Singleton. Esto funciona porque después de la primera vez que se importa un archivo, Python no vuelve a ejecutar el código.


44
Principalmente cierto, pero a veces eso no es suficiente. Por ejemplo, tengo un proyecto con la necesidad de registrar instancias de muchas clases a nivel DEBUG. Necesito analizar las opciones de línea de comandos al inicio para establecer el nivel de registro especificado por el usuario antes de que se instancian esas clases. Las instancias a nivel de módulo hacen que eso sea problemático. Es posible que pueda estructurar cuidadosamente la aplicación para que todas esas clases no se importen hasta que se complete el procesamiento de CLI, pero la estructura natural de mi aplicación es más importante que la adherencia dogmática a "los singletons son malos", ya que pueden hecho de manera bastante limpia.
CryingCyclops

si probaras tu código mientras aplicabas parches a my_singleton, ¿sería posible? ya que este my_singleton podría ser instanciado en algún otro módulo.
Naveen

@Naveen: my_singleton es un solo objeto. Si lo "parcheas", ese cambio afectará a todas las referencias futuras, incluso en otros módulos.
Alan Dyke

16

Aquí hay una frase para usted:

singleton = lambda c: c()

Así es como lo usa:

@singleton
class wat(object):
    def __init__(self): self.x = 1
    def get_x(self): return self.x

assert wat.get_x() == 1

Su objeto se instancia con entusiasmo. Esto puede o no ser lo que quieres.


55
-1. Esto es muy malo y tonto. No define una clase para usar como objeto. Ya no puedes acceder a la clase sin ser muy feo como type(wat)o wat.__class__. Si realmente desea lograr esto, defina mejor la clase e instanciarla de inmediato, sin necesidad de meterse con el decorador.
0xc0de

2
¿Por qué necesitas saber usar la clase de un singleton? Solo use el objeto singleton ..
Tolli

1
No es un patrón singleton , por lo que la función IMO debería llamarse de manera diferente.
GingerPlusPlus

8
Wikipedia: "el patrón singleton es un patrón de diseño que restringe la creación de instancias de una clase a un objeto". Yo diría que mi solución hace exactamente eso. De acuerdo, supongo que se podría hacer wat2 = type(wat)(), pero esto es python, todos estamos consintiendo adultos y todo eso. No puede garantizar que solo habrá una instancia, pero puede garantizar que si las personas hacen una segunda, se verá fea y, si son personas decentes y respetuosas, como una señal de advertencia para ellos. ¿Qué me estoy perdiendo?
Jonas Kölker


3

Aquí está mi propia implementación de singletons. Todo lo que tienes que hacer es decorar la clase; para obtener el singleton, debes usar el Instancemétodo. Aquí hay un ejemplo:

   @Singleton
   class Foo:
       def __init__(self):
           print 'Foo created'

   f = Foo() # Error, this isn't how you get the instance of a singleton

   f = Foo.Instance() # Good. Being explicit is in line with the Python Zen
   g = Foo.Instance() # Returns already created instance

   print f is g # True

Y aquí está el código:

class Singleton:
    """
    A non-thread-safe helper class to ease implementing singletons.
    This should be used as a decorator -- not a metaclass -- to the
    class that should be a singleton.

    The decorated class can define one `__init__` function that
    takes only the `self` argument. Other than that, there are
    no restrictions that apply to the decorated class.

    To get the singleton instance, use the `Instance` method. Trying
    to use `__call__` will result in a `TypeError` being raised.

    Limitations: The decorated class cannot be inherited from.

    """

    def __init__(self, decorated):
        self._decorated = decorated

    def Instance(self):
        """
        Returns the singleton instance. Upon its first call, it creates a
        new instance of the decorated class and calls its `__init__` method.
        On all subsequent calls, the already created instance is returned.

        """
        try:
            return self._instance
        except AttributeError:
            self._instance = self._decorated()
            return self._instance

    def __call__(self):
        raise TypeError('Singletons must be accessed through `Instance()`.')

    def __instancecheck__(self, inst):
        return isinstance(inst, self._decorated)

2
No es cierto singleton. SingletonList = Singleton(list).Instance(); print(SingletonList is type(SingletonList)())debe imprimir Trueen verdadero singleton; con su códigoFalse
impreso

@GingerPlusPlus Era consciente de algunas limitaciones, pero no de la que usted señaló. Gracias por mencionarlo. Desafortunadamente, no tengo tiempo en este momento para pensar en una solución a esto.
Paul Manta

2

El método 3 parece ser muy bueno, pero si desea que su programa se ejecute tanto en Python 2 como en Python 3 , no funciona. Incluso la protección de las variantes separadas con pruebas para la versión de Python falla, porque la versión de Python 3 da un error de sintaxis en Python 2.

Gracias a Mike Watkins: http://mikewatkins.ca/2008/11/29/python-2-and-3-metaclasses/ . Si desea que el programa funcione tanto en Python 2 como en Python 3, debe hacer algo como:

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

MC = Singleton('MC', (object), {})

class MyClass(MC):
    pass    # Code for the class implementation

Supongo que el 'objeto' en la asignación debe reemplazarse con la 'BaseClass', pero no lo he intentado (he intentado el código como se ilustra).


seguramente esto no es una metaclase - en python3 usar una metaclase para construir MyClass lo haríaclass MyClass(metaclass=Singleton)
theheadofabroom

El enlace mikewatkins.ca está (efectivamente) roto.
Peter Mortensen

1

Bueno, aparte de estar de acuerdo con la sugerencia general de Pythonic sobre tener un nivel de módulo global, ¿qué tal esto:

def singleton(class_):
    class class_w(class_):
        _instance = None
        def __new__(class2, *args, **kwargs):
            if class_w._instance is None:
                class_w._instance = super(class_w, class2).__new__(class2, *args, **kwargs)
                class_w._instance._sealed = False
            return class_w._instance
        def __init__(self, *args, **kwargs):
            if self._sealed:
                return
            super(class_w, self).__init__(*args, **kwargs)
            self._sealed = True
    class_w.__name__ = class_.__name__
    return class_w

@singleton
class MyClass(object):
    def __init__(self, text):
        print text
    @classmethod
    def name(class_):
        print class_.__name__

x = MyClass(111)
x.name()
y = MyClass(222)
print id(x) == id(y)

Salida es:

111     # the __init__ is called only on the 1st time
MyClass # the __name__ is preserved
True    # this is actually the same instance

¿Cuál es el punto del _sealedatributo? Por lo que veo, ¿esto no hace nada? Algo me molesta sobre esto que dice que no debería funcionar bien ... Haré algunas pruebas comparativas más adelante esta semana.
theheadofabroom

_sealed asegura que tu init se ejecuta solo una vez; No veo ningún punto por el que debería funcionar peor que el decorador similar a una función regular: la función se ejecuta solo una vez por clase y devuelve una nueva clase heredada
Guard

Por cierto, su edición contiene pestañas que rompen las sangrías También dice 'estamos creando 2 clases', ¿quiere decir que estamos creando '1 clase adicional'?
Guardia

Sí, una clase extra es lo que quise decir. Tengo la intención de incluir cosas __init__para que se llamen cada vez que se inicializa. Solo un simple 'Ser inicializado en class.method'. en cuanto a la sangría (usó pestañas y espacios), arreglé la mayor parte, pero parece haber perdido una si desea obtenerla (solo consulte el registro de edición)
theheadofabroom

re init : por supuesto, depende de usted, solo traté de imitar el comportamiento singleton en otros idiomas, donde el código del constructor (que no es exactamente init , pero muy cercano en su sentido) solo se llama una vez si desea el init estar se llama cada vez, simplemente elimine todas las referencias a espacios / pestañas re sellados, bueno, entonces mi emacs necesita reparación. de todos modos, arriba está la versión corregida
Guard

1

Qué tal esto:

def singleton(cls):
    instance=cls()
    cls.__new__ = cls.__call__= lambda cls: instance
    cls.__init__ = lambda self: None
    return instance

Úselo como decorador en una clase que debería ser un singleton. Me gusta esto:

@singleton
class MySingleton:
    #....

Esto es similar al singleton = lambda c: c()decorador en otra respuesta. Al igual que la otra solución, la única instancia tiene el nombre de la clase ( MySingleton). Sin embargo, con esta solución aún puede "crear" instancias (en realidad obtener la única instancia) de la clase, al hacerlo MySingleton(). También evita que crees instancias adicionales haciendo type(MySingleton)()(eso también devuelve la misma instancia).


No define una clase para usarla como un objeto.
0xc0de

1
Cada vez que llama type(MySingleton)(), MySingleton.__init__()recibe una llamada y el objeto se inicializa varias veces; puedes arreglarlo escribiendo cls.__init__ = lambda self: passen tu singleton. Además, la anulación cls.__call__parece inútil e incluso dañina: __call__definida en este contexto se usa cuando llamas MySingleton(any, list, of, arguments), no cuando llamas type(MySingleton)(any, list, of, arguments).
GingerPlusPlus

@GingerPlusPlus, gracias por señalar que se __init__()vuelve a llamar al hacerlo type(MySingleton)(). La solución que propuso (agregar cls.__init__ = lambda self: pass) da un error de sintaxis, porque la última parte de la expresión lambda debe ser una expresión, no una declaración. Sin embargo, agregar cls.__init__ = lambda self: Nonetrabajos, así que agregué eso a mi respuesta.
Tolli

1
@GingerPlusPlus, sobre el uso de __call__. mi intención era hacer ambas cosas type(MySingleton)()y MySingleton()devolver la instancia. Entonces está haciendo lo que quería. Puede pensar en MySingleton como el tipo de singleton o la instancia de singleton (o ambos).
Tolli

1

Tiraré el mío al ring. Es un simple decorador.

from abc import ABC

def singleton(real_cls):

    class SingletonFactory(ABC):

        instance = None

        def __new__(cls, *args, **kwargs):
            if not cls.instance:
                cls.instance = real_cls(*args, **kwargs)
            return cls.instance

    SingletonFactory.register(real_cls)
    return SingletonFactory

# Usage
@singleton
class YourClass:
    ...  # Your normal implementation, no special requirements.

Beneficios que creo que tiene sobre algunas de las otras soluciones:

  • Es claro y conciso (para mi ojo; D).
  • Su acción está completamente encapsulada. No necesita cambiar nada sobre la implementación deYourClass . Esto incluye no necesitar usar una metaclase para su clase (tenga en cuenta que la metaclase anterior está en la fábrica, no la clase "real").
  • No se basa en nada de parches de mono.
  • Es transparente para las personas que llaman:
    • Las personas que llaman todavía simplemente importan YourClass, parece una clase (porque lo es), y lo usan normalmente. No es necesario adaptar las llamadas a una función de fábrica.
    • Lo que YourClass()crea una instancia sigue siendo una verdadera instancia de lo YourClassque implementó, no un proxy de ningún tipo, por lo que no hay posibilidad de que se produzcan efectos secundarios.
    • isinstance(instance, YourClass) y operaciones similares todavía funcionan como se esperaba (aunque este bit requiere abc, por lo que excluye Python <2.6).

Se me ocurre una desventaja: los métodos de clase y los métodos estáticos de la clase real no se pueden llamar de forma transparente a través de la clase de fábrica que lo oculta. He usado esto raramente lo suficiente como para nunca encontrarme con esa necesidad, pero sería fácilmente rectificado usando una metaclase personalizada en la fábrica que implemente__getattr__() para delegar el acceso a todos los atributos a la clase real.

Un patrón relacionado que realmente he encontrado más útil (no es que esté diciendo que este tipo de cosas se requieren con mucha frecuencia) es un patrón "Único" en el que instanciar la clase con los mismos argumentos resulta en recuperar la misma instancia. Es decir, un "singleton por argumentos". Lo anterior se adapta bien a esto y se vuelve aún más conciso:

def unique(real_cls):

    class UniqueFactory(ABC):

        @functools.lru_cache(None)  # Handy for 3.2+, but use any memoization decorator you like
        def __new__(cls, *args, **kwargs):
            return real_cls(*args, **kwargs)

    UniqueFactory.register(real_cls)
    return UniqueFactory

Dicho todo esto, estoy de acuerdo con el consejo general de que si cree que necesita una de estas cosas, probablemente debería detenerse por un momento y preguntarse si realmente lo necesita. 99% del tiempo, YAGNI.


1
  • Si se quiere tener múltiples instancias de la misma clase, pero solo si los args o kwargs son diferentes, se puede usar esto
  • Ex.
    1. Si tienes un manejo de clase serial comunicación de , y para crear una instancia que desea enviar el puerto serie como argumento, entonces con el enfoque tradicional no funcionará
    2. Usando los decoradores mencionados anteriormente, uno puede crear múltiples instancias de la clase si los argumentos son diferentes.
    3. Para los mismos argumentos, el decorador devolverá la misma instancia que ya se ha creado.
>>> from decorators import singleton
>>>
>>> @singleton
... class A:
...     def __init__(self, *args, **kwargs):
...         pass
...
>>>
>>> a = A(name='Siddhesh')
>>> b = A(name='Siddhesh', lname='Sathe')
>>> c = A(name='Siddhesh', lname='Sathe')
>>> a is b  # has to be different
False
>>> b is c  # has to be same
True
>>>

0

Código basado en la respuesta de Tolli .

#decorator, modyfies new_cls
def _singleton(new_cls):
    instance = new_cls()                                              #2
    def new(cls):
        if isinstance(instance, cls):                                 #4
            return instance
        else:
            raise TypeError("I can only return instance of {}, caller wanted {}".format(new_cls, cls))
    new_cls.__new__  = new                                            #3
    new_cls.__init__ = lambda self: None                              #5
    return new_cls


#decorator, creates new class
def singleton(cls):
    new_cls = type('singleton({})'.format(cls.__name__), (cls,), {} ) #1
    return _singleton(new_cls)


#metaclass
def meta_singleton(name, bases, attrs):
    new_cls = type(name, bases, attrs)                                #1
    return _singleton(new_cls)

Explicación:

  1. Crea una nueva clase, heredando de dado cls
    (no se modifica clsen caso de que alguien quiera, por ejemplo singleton(list))

  2. Crear instancia Antes de anular __new__es tan fácil.

  3. Ahora, cuando hemos creado fácilmente una instancia, se anula __new__utilizando el método definido hace un momento.
  4. La función regresa instancesolo cuando es lo que la persona que llama espera, de lo contrario aumenta TypeError.
    La condición no se cumple cuando alguien intenta heredar de la clase condecorada.

  5. Si __new__()devuelve una instancia de cls, __init__()se invocará el método de la nueva instancia como __init__(self[, ...]), donde self es la nueva instancia y los argumentos restantes son los mismos a los que se pasó __new__().

    instanceya está inicializado, por lo que la función reemplaza __init__a la función que no hace nada.

Véalo trabajando en línea


0

Es ligeramente similar a la respuesta de fab, pero no exactamente igual.

El contrato singleton no requiere que podamos llamar al constructor varias veces. Como un singleton debe crearse una vez y solo una vez, ¿no debería verse que se creó solo una vez? La "suplantación" del constructor podría afectar la legibilidad.

Entonces mi sugerencia es solo esto:

class Elvis():
    def __init__(self):
        if hasattr(self.__class__, 'instance'):
            raise Exception()
        self.__class__.instance = self
        # initialisation code...

    @staticmethod
    def the():
        if hasattr(Elvis, 'instance'):
            return Elvis.instance
        return Elvis()

Esto no excluye el uso del constructor o el campo instancepor el código de usuario:

if Elvis() is King.instance:

... si sabe con certeza que Elvisaún no se ha creado, y que sí King.

Pero alienta a los usuarios a usar el themétodo universalmente:

Elvis.the().leave(Building.the())

Para completar esto, también puede anular __delattr__()para generar una Excepción si se intenta eliminar instance, y anular __del__()para que genere una Excepción (a menos que sepamos que el programa está finalizando ...)

Futuras mejoras


Mi agradecimiento a aquellos que han ayudado con comentarios y ediciones, de los cuales más son bienvenidos. Si bien uso Jython, esto debería funcionar de manera más general y ser seguro para subprocesos.

try:
    # This is jython-specific
    from synchronize import make_synchronized
except ImportError:
    # This should work across different python implementations
    def make_synchronized(func):
        import threading
        func.__lock__ = threading.Lock()

        def synced_func(*args, **kws):
            with func.__lock__:
                return func(*args, **kws)

        return synced_func

class Elvis(object): # NB must be subclass of object to use __new__
    instance = None

    @classmethod
    @make_synchronized
    def __new__(cls, *args, **kwargs):
        if cls.instance is not None:
            raise Exception()
        cls.instance = object.__new__(cls, *args, **kwargs)
        return cls.instance

    def __init__(self):
        pass
        # initialisation code...

    @classmethod
    @make_synchronized
    def the(cls):
        if cls.instance is not None:
            return cls.instance
        return cls()

Puntos de nota:

  1. Si no subclasifica del objeto en python2.x obtendrá una clase de estilo antiguo, que no utiliza __new__
  2. Al decorar __new__debe decorar con @classmethod o __new__será un método de instancia independiente
  3. Esto podría mejorarse mediante el uso de una metaclase, ya que esto le permitiría crear theuna propiedad de nivel de clase, posiblemente renombrándola ainstance

Si bien esta es una interpretación ligeramente diferente del patrón singleton, estoy bastante seguro de que sigue siendo válido, aunque podría estar tentado a usar __new__ en lugar de hacerlo __init__, ya que actúa puramente sobre los atributos de clase y esto evita que haya una segunda instancia brevemente. La diferencia entre este y el método 2 es si intentar inicializar más de una vez devuelve la instancia única o genera una excepción. Creo que estoy contento de que o satisfaga el patrón singleton, uno es más fácil de usar, mientras que el otro es más explícito que es un singleton.
theheadofabroom

Obviamente el uso del nombre de la clase en __init__previene la subclasificación, pero al mismo tiempo esto hace las cosas más fáciles, no se requiere
theheadofabroom

Gracias ... ah sí, una segunda instancia momentánea antes de que se lance la excepción. He modificado el __init__para que con suerte esto sea subclassable ...
Mike rodent

Genial, theprobablemente podría beneficiarse de ser un método de clase por razones similares
theheadofabroom

sí tienes razón. Entonces puede tener un singleton de subclase de SuperElvis y (por ejemplo) un singleton de subclase de ImaginaryElvis ... y pueden coexistir. Ver pensamientos adicionales. Por favor, siéntase libre de mejorar mi código.
Mike roedor

0

One liner (no estoy orgulloso, pero hace el trabajo):

class Myclass:
  def __init__(self):
      # do your stuff
      globals()[type(self).__name__] = lambda: self # singletonify

Esto puede hacer el trabajo siempre que su clase no se haya importado a ningún otro módulo ...
Aran-Fey

Cierto. Si puede pagar el costo de iniciar la clase, puede ejecutar un Myclass () inmediatamente después de la definición de la clase para evitar esto.
polvoazul

0

Si no necesita una inicialización diferida de la instancia de Singleton, lo siguiente debería ser fácil y seguro para subprocesos:

class A:
    instance = None
    # Methods and variables of the class/object A follow
A.instance = A()

De esta manera, Ase inicia un singleton en la importación del módulo.


0

Tal vez no entiendo el patrón singleton, pero mi solución es simple y pragmática (¿pitónica?). Este código cumple dos objetivos.

  1. Haga la instancia de Fooaccesible en todas partes (global).
  2. Solo Foopuede existir una instancia de .

Este es el código.

#!/usr/bin/env python3

class Foo:
    me = None

    def __init__(self):
        if Foo.me != None:
            raise Exception('Instance of Foo still exists!')

        Foo.me = self


if __name__ == '__main__':
    Foo()
    Foo()

Salida

Traceback (most recent call last):
  File "./x.py", line 15, in <module>
    Foo()
  File "./x.py", line 8, in __init__
    raise Exception('Instance of Foo still exists!')
Exception: Instance of Foo still exists!

0

Después de luchar con esto durante algún tiempo, finalmente se me ocurrió lo siguiente, de modo que el objeto de configuración solo se cargaría una vez, cuando se lo llama desde módulos separados. La metaclase permite que una instancia de clase global se almacene en el dicticio integrado, que en la actualidad parece ser la mejor manera de almacenar un programa global adecuado.

import builtins

# -----------------------------------------------------------------------------
# So..... you would expect that a class would be "global" in scope, however
#   when different modules use this,
#   EACH ONE effectively has its own class namespace.  
#   In order to get around this, we use a metaclass to intercept
#   "new" and provide the "truly global metaclass instance" if it already exists

class MetaConfig(type):
    def __new__(cls, name, bases, dct):
        try:
            class_inst = builtins.CONFIG_singleton

        except AttributeError:
            class_inst = super().__new__(cls, name, bases, dct)
            builtins.CONFIG_singleton = class_inst
            class_inst.do_load()

        return class_inst

# -----------------------------------------------------------------------------

class Config(metaclass=MetaConfig):

    config_attr = None

    @classmethod
    def do_load(cls):
        ...<load-cfg-from-file>...

-1

No recuerdo dónde encontré esta solución, pero creo que es la más 'elegante' desde mi punto de vista no experto en Python:

class SomeSingleton(dict):
    __instance__ = None
    def __new__(cls, *args,**kwargs):
        if SomeSingleton.__instance__ is None:
            SomeSingleton.__instance__ = dict.__new__(cls)
        return SomeSingleton.__instance__

    def __init__(self):
        pass

    def some_func(self,arg):
        pass

¿Por qué me gusta esto? Sin decoradores, sin metaclases, sin herencia múltiple ... y si decides que ya no quieres que sea Singleton, simplemente elimina el __new__método. Como soy nuevo en Python (y OOP en general) espero que alguien me aclare por qué este es un enfoque terrible.


2
¿Por qué este es un enfoque terrible? cuando desee crear otra clase singleton, debe copiar y pegar el __new__. No te repitas a ti mismo .
GingerPlusPlus

Además, por las que su nueva toma *argsy **kwargs, a continuación, no hace nada con ellos? Pasarlos en dict.__new__esta manera: dict.__new__(cls, *args, **kwargs).
GingerPlusPlus

Esto llamará al __init__método cada vez que se llame a la clase. Si su __init__método realmente hizo algo, notará el problema. Siempre que lo haga SomeSingleton(), el __init__método restablecerá el estado de su singleton .
Aran-Fey

-2

Esta es mi forma preferida de implementar singletons:

class Test(object):
    obj = None

    def __init__(self):
        if Test.obj is not None:
            raise Exception('A Test Singleton instance already exists')
        # Initialization code here

    @classmethod
    def get_instance(cls):
        if cls.obj is None:
            cls.obj = Test()
        return cls.obj

    @classmethod
    def custom_method(cls):
        obj = cls.get_instance()
        # Custom Code here

1
Esto no es estrictamente un singleton ya que permite que exista más de una instancia de la clase. Una mejora sería hacer que la clase no se pueda inicializar y que todos los métodos de clase actúen sobre los atributos de la clase
theheadofabroom

-2

Es probable que esta respuesta no sea lo que estás buscando. Quería un singleton en el sentido de que solo ese objeto tuviera su identidad, para compararlo. En mi caso, se estaba utilizando como valor centinela . Para lo cual la respuesta es muy simple, haga cualquier objeto mything = object()y, por naturaleza de Python, solo esa cosa tendrá su identidad.

#!python
MyNone = object()  # The singleton

for item in my_list:
    if item is MyNone:  # An Example identity comparison
        raise StopIteration

Aprendí que los módulos pueden importarse varias veces, en tal caso esto es solo un singleton local, que en realidad no es un singleton en ninguna capacidad.
ThorSummoner

¿Puede explicar cómo se puede importar un módulo varias veces? La única vez que he visto eso es cuando ocurre una excepción mientras el módulo está cargado, el usuario aún puede cargar el módulo más tarde, pero los efectos secundarios ya se habrán producido, por lo que algunas acciones pueden ejecutarse por segunda vez.
sleblanc

Una vez que un módulo se ha cargado por completo, no veo una manera de ejecutarlo nuevamente, aparte de obligar al intérprete a hacerlo con evalo importlib.reload.
sleblanc

-3

Esta solución causa cierta contaminación del espacio de nombres a nivel de módulo (tres definiciones en lugar de solo una), pero me resulta fácil de seguir.

Me gustaría poder escribir algo como esto (inicialización diferida), pero desafortunadamente las clases no están disponibles en el cuerpo de sus propias definiciones.

# wouldn't it be nice if we could do this?
class Foo(object):
    instance = None

    def __new__(cls):
        if cls.instance is None:
            cls.instance = object()
            cls.instance.__class__ = Foo
        return cls.instance

Como eso no es posible, podemos dividir la inicialización y la instancia estática en

Inicialización ansiosa:

import random


class FooMaker(object):
    def __init__(self, *args):
        self._count = random.random()
        self._args = args


class Foo(object):
    def __new__(self):
        return foo_instance


foo_instance = FooMaker()
foo_instance.__class__ = Foo

Inicialización perezosa:

Inicialización ansiosa:

import random


class FooMaker(object):
    def __init__(self, *args):
        self._count = random.random()
        self._args = args


class Foo(object):
    def __new__(self):
        global foo_instance
        if foo_instance is None:
            foo_instance = FooMaker()
        return foo_instance


foo_instance = None
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.