Pregunta Creando un singleton en Python


Esta pregunta no es para la discusión de si el patrón de diseño singleton es deseable, es un anti-patrón, o para cualquier guerra religiosa, pero para discutir cómo este patrón se implementa mejor en Python de tal manera que es más pitónico. En este caso, defino 'más pitonica' para significar que sigue el 'principio de menor asombro'.

Tengo varias clases que se convertirán en singletons (mi caso de uso es para un registrador, pero esto no es importante). No deseo agrupar varias clases con una extravagancia añadida cuando puedo simplemente 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

  • Mientras que los objetos creados usando MyClass () serían verdaderos objetos singleton, MyClass en sí mismo es una función, no una clase, por lo que no puede invocar métodos de clase desde él. También por m = MyClass(); n = MyClass(); o = type(n)(); entonces m == 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: A metaclass

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 lo hizo saber)

Contras

  • ¿Hay alguno?

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 se pueda escalar. Por supuesto, hay una cuestión de debate sobre si es demasiado fácil escalar este patrón ...
  • ¿Cuál es el objetivo de la _sealed atributo
  • No se pueden llamar a los métodos del mismo nombre en las clases base usando super() porque ellos recurrirán Esto significa que no puedes personalizar __new__ y no puede subclasificar una clase que necesita que llame para __init__.

560
2017-07-20 10:47


origen


Respuestas:


Usa una Metaclass

yo recomendaria Método n. ° 2, pero es mejor usar una metaclass que una clase base. Aquí hay una implementación de ejemplo:

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 quieres correr __init__ cada vez que se llama a la clase, agregue

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

al if declaración en Singleton.__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. Logger en el código de arriba será de tipo class 'your_module.Singleton', al igual que la (única) instancia de Logger será de tipo class 'your_module.Logger'. Cuando llamas al registrador con Logger(), Python primero pregunta la metaclase de Logger, Singleton, qué hacer, permitiendo que la creación de la instancia sea eliminada. Este proceso es el mismo que Python preguntando a una clase qué hacer llamando __getattr__ cuando haces referencia a uno de sus atributos haciendo myclass.attribute.

Una metaclass esencialmente decide qué significa la definición de clase y cómo implementar esa definición. Ver por ejemplo http://code.activestate.com/recipes/498149/, que esencialmente recrea el estilo C structs en Python usando metaclases. La amenaza ¿Cuáles son sus casos de uso (concretos) para las metaclases en Python? también proporciona algunos ejemplos, por lo general, parecen estar relacionados con la programación declarativa, especialmente cuando se usan en ORM.

En esta situación, si usa su Método n. ° 2, y una subclase define una __new__ método, será ejecutado todo el tiempo llama SubClassOfSingleton() - porque es responsable de llamar al método que devuelve la instancia almacenada. Con una metaclase, lo hará solo ser llamado una vez, cuando se crea la única instancia. Tú quieres 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 es creado solo una vez, y una metaclase es la forma de personalizar el creación de una clase. Usar una metaclase te da mas control en caso de que necesite personalizar las definiciones de clase singleton de otras maneras.

Sus singletons no necesitará herencia múltiple (porque la metaclase no es una clase base), sino para subclases de la clase creada que usan herencia múltiple, debes asegurarte de que la clase singleton sea la primero / más a la izquierda uno con una metaclase que redefine __call__ Esto es muy poco probable que sea un problema. El dict de instancia es no en el espacio de nombres de la instancia por lo que no lo sobreescribirá accidentalmente.

También escuchará que el patrón singleton viola el "Principio de Responsabilidad Individual" - cada clase debería hacer Sólo una cosa. De esta forma, 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 la metaclase pasa esta prueba. La metaclase es responsable de haciendo cumplir el patrón y la clase y subclases creadas no necesitan ser consciente de que son singletons. Método 1 falla esta prueba, como notó con "MyClass en sí es una función, no una clase, por lo que no puede invocar métodos de clase".

Versión compatible Python 2 y 3

Escribir algo que funcione tanto en Python2 como en 3 requiere un esquema un poco más complicado. Dado que las metaclases suelen ser subclases de tipo type, es posible usar uno para crear dinámicamente una clase base intermedia en tiempo de ejecución con ella como su metaclase y luego usar ese como la clase de base del público Singleton 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 usa subclases para implementar una metaclase. Una posible ventaja es que, a diferencia de una metaclass pura, isinstance(inst, Singleton)regresará True.

Correcciones

En otro tema, probablemente ya haya notado esto, pero la implementación de la clase base en su publicación original es incorrecta. _instances necesita ser referenciado en la clase, necesitas usar super() o eres recursivoy __new__ es en realidad un método estático que tienes que pasar la clase a, no es un método de clase, como la clase real no ha sido creado sin embargo, cuando se llama. Todas estas cosas serán verdaderas 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 regresando una clase

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

Los principales problemas provienen de que la clase es su propia clase base. En primer lugar, ¿no es raro 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 puedes definir cualquier método que llame al método del mismo nombre en su clase base con super() porque ellos recurrirán Esto significa que tu clase no puede personalizar __new__y no puede derivar de ninguna clase que necesite __init__ los llamó.

Cuándo usar el patrón singleton

Tu 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 ha sido un candidato natural para Singletons". Eres absolutamente correcto.

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

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

La segunda excepción, que se menciona menos, es lo opuesto: cuando el singleton es solo un receptor de datos, no una fuente de datos (directa o indirectamente). Esta es la razón por la cual los madereros se sienten como un uso "natural" para los singletons. Como los diversos usuarios son no cambiando los registradores de manera que a otros usuarios les importe, hay estado realmente no compartido. Esto niega el argumento principal frente al patrón singleton, y los convierte en una elecció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. Es un singleton donde todos los objetos alcanzables son inmutables. Si todos los objetos son inmutables, Singleton no tiene estado global, ya que todo es constante. Pero es tan fácil convertir este tipo de singleton en uno mutable, es una pendiente muy resbaladiza. Por lo tanto, también estoy en contra de estos Singleton, 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 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 ya sea que un registrador determinado esté habilitado o no. La información aquí fluye de una manera: desde su aplicación al registrador. Incluso los registradores de ideas 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 madereros no son dañinos a pesar de estar llenos de estado.


386
2017-07-23 03:28



class Foo(object):
     pass

some_global_variable = Foo()

Los módulos se importan solo una vez, todo lo demás es pensamiento excesivo. No use singletons y trate de no usar globals.


60
2017-07-20 10:52



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' de singleton.


49
2017-07-20 10:58



Probablemente nunca necesites 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, yo 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 que My_Singleton está definido. Esto funciona porque después de la primera vez que se importa un archivo, Python no vuelve a ejecutar el código.


12
2017-12-10 22:24



Aquí hay un trazador de líneas 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 obtiene una instancia con entusiasmo. Esto puede o no ser lo que quieres.


10
2017-10-19 14:43



Mira la pregunta de desbordamiento de pila ¿Existe una manera simple y elegante de definir singletons en Python? con varias soluciones.

Recomiendo encarecidamente ver las charlas de Alex Martelli sobre patrones de diseño en python: parte 1 y parte 2. En particular, en la parte 1 habla sobre objetos individuales / objetos de estado compartido.


5
2017-07-20 11:00



Aquí está mi propia implementación de singletons. Todo lo que tienes que hacer es decorar la clase; para obtener el singleton, entonces debes usar el Instance mé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)

3
2017-09-20 18:04



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)

La salida es:

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

2
2017-07-25 00:09



El Método 3 parece ser muy claro, pero si desea que su programa se ejecute en ambos Python 2 y Python 3, no funciona Incluso la protección de las variantes separadas con las pruebas para la versión Python falla, porque la versión 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 tarea necesita ser reemplazado por 'BaseClass', pero no lo he intentado (he intentado con el código como se muestra).


2
2017-07-24 17:06