patron - python singleton metaclass




Creando un singleton en Python (13)

Usa una metaclase

Recomendaría el Método # 2 , pero es mejor usar una metaclase 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 desea ejecutar __init__ cada vez que se llama a la clase, agregue

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

a la sentencia if en Singleton.__call__ .

Unas palabras sobre las metaclases. Una metaclase es la clase de una clase ; es decir, una clase es una instancia de su metaclase . Encontrará 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 anterior será de la class 'your_module.Singleton' de tipo class 'your_module.Singleton' , así como la (única) instancia de Logger será de la class 'your_module.Logger' de tipo class 'your_module.Logger' . Cuando llama al registrador con el Logger() , Python primero le pregunta a la metaclase del Logger , Singleton , qué hacer, permitiendo que la creación de instancias se anule. Este proceso es el mismo que Python que le pregunta a una clase qué hacer al llamar a __getattr__ cuando hace referencia a uno de sus atributos al hacer myclass.attribute .

Una metaclase esencialmente decide lo que significa la definición de una clase y cómo implementar esa definición. Véase, por ejemplo, http://code.activestate.com/recipes/498149/ , que esencialmente recrea las struct estilo C en Python utilizando metaclases. El hilo ¿Cuáles son sus casos de uso (concretos) para metaclases en Python? También proporciona algunos ejemplos, generalmente parecen estar relacionados con la programación declarativa, especialmente cuando se usan en ORMs.

En esta situación, si usa su Método # 2 , y una subclase define un método __new__ , se ejecutará cada vez que llame a 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 se personaliza la creación de una clase . El uso de una metaclase le da 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 redefine __call__ Es muy poco probable que esto sea un problema. El dictado de la instancia no está en el espacio de nombres de la instancia, por lo que no se sobrescribirá accidentalmente.

También escuchará que el patrón de singleton viola el "Principio de Responsabilidad Única" - cada clase debe hacer solo una cosa . De esa manera, no tiene que preocuparse por arruinar 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 imponer el patrón y la clase y las subclases creadas no deben ser conscientes de que son singletons . El método # 1 no pasa esta prueba, como anotó con "MyClass en sí misma es una función, no una clase, por lo que no puede llamar 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 un esquema un poco más complicado. Dado que las metaclases suelen ser subclases de tipo de type , es posible usar una para crear dinámicamente una clase base intermediaria en tiempo de ejecución con ella como su metaclase y luego usarla como la clase base de la clase base pública Singleton . 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) devolverá 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 deben ser referenciadas en la clase , debes usar super() o estás recurriendo , y __new__ es en realidad un método estático al que debes pasar la clase , no un método de clase, ya que la clase real no tiene Se ha creado aún cuando se llama. Todas estas cosas también serán ciertas para una implementación de metaclase.

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í. El método # 4 es mejor que la otra versión de decorador, pero es más código del necesario para un singleton, y no es tan claro lo que hace.

Los principales problemas provienen de 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 atributo __class__ ? Esto también significa que no puede definir ningún método que llame al método del mismo nombre en su clase base con super() porque recursionarán. Esto significa que su clase no puede personalizar __new__ , y no puede derivar de ninguna clase que necesite __init__ llamada.

Cuando usar el patrón singleton

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

Cuando la gente dice que los singletons son malos, la razón más común es que son estados compartidos implícitos . Mientras que las variables globales y las importaciones de módulos de nivel superior son estados compartidos explícitos , generalmente se crea una instancia de otros objetos que se transmiten. 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 las enumeraciones, se acepta ampliamente y se considera sensato porque, sin importar qué, ninguno de los usuarios puede desordenarlos con otros usuarios . Esto es igualmente cierto para un singleton constante.

La segunda excepción, que se menciona menos, es la opuesta: cuando el singleton es solo un receptor de datos , no una fuente de datos (directa o indirectamente). Esta es la razón por la que los madereros se sienten como un uso "natural" para singletons. Como los diferentes usuarios no están cambiando los registradores de manera que otros usuarios se preocupen, realmente no hay estado compartido . Esto niega el argumento principal en contra del patrón de 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 alcanzables 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 uno mutable, es una pendiente muy resbaladiza. Por lo tanto, yo 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 son solo este tipo de singletons. Siempre que no ponga un 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. Se carga 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 dado esté habilitado o no. La información aquí fluye de una manera: desde su aplicación al registrador. Incluso los registradores de pensamiento son estados globales, ya que no hay información que fluya de los registradores a su aplicación, los registradores son aceptables Aún debe inyectar su registrador si desea que su prueba afirme que algo se está registrando, pero en general los registradores no son perjudiciales a pesar de estar llenos de estado.

Esta pregunta no es para la discusión de si el patrón de diseño de singleton es deseable o no, es un antipatrón o para cualquier guerra religiosa, sino para discutir cómo este patrón se implementa mejor en Python de tal manera que sea la mayoría de los pitónicos. En este caso, defino que "la mayoría de los pitones" significa que sigue el "principio de menos 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 desordenar varias clases con gumph agregado 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

  • Mientras que los objetos creados usando MyClass () serían verdaderos objetos singleton, MyClass en sí misma es una función, no una clase, por lo que no se pueden llamar métodos de clase. También para m = MyClass(); n = MyClass(); o = type(n)(); 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 ser sobrescrito durante la herencia de una segunda clase base? Hay que pensar más de lo necesario.

Método 3: una 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.
  • Utiliza __metaclass__ para su propósito apropiado (y me hizo consciente de ello)

Contras

  • Hay alguna

Método 4: decorador devolviendo 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 atributo _sealed
  • No se pueden llamar a los métodos del mismo nombre en las clases base mediante super() porque se repetirán. Esto significa que no puede personalizar __new__ y no puede subclasificar una clase que necesite que llame a __init__ .

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

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


Las respuestas anteriores son correctas, pero no estoy de acuerdo con la declaración que se publicó como parte de la pregunta en el Método 1 "MyClass en sí misma es una función, no una clase, por lo que no puede llamar a los métodos de clase". Vea mi ejemplo a continuación que el método se llama varias veces, en MyClass, que está decorado con una etiqueta singleton.

Además, tenga en cuenta que esto es muy similar a algunas de las respuestas publicadas y se basa en el documento de Python, pero es ligeramente diferente porque la clase y la función están diseñadas de manera que pueden recibir 1 o 0 argumentos y aún así funciona singleton.

Aquí está la prueba de que puede llamar a los métodos de singleton varias veces y muestra que una instancia de la clase todavía se usa y no se crean nuevos objetos.

#/usr/bin/env python

def singleton(cls):
    instances = {}
    def getinstance(anyArgs=None):
        if cls not in instances:
            instances[cls] = cls(anyArgs)
        return instances[cls]
    return getinstance

@singleton
class MyClass:
    def __init__(self,count=None):
        print("print argument if any exists",count)

    def test(self, counter):
        # time.sleep(1000)
        print("-->",counter)
        return counter

### create two objects to see if we get a singleton behavior !
a = MyClass(10000)
a.test(1)
b = MyClass()
b.test(2)
if a != b:
    print("this is not a singleton")

#lets makesure it's still is the same object
if a!=b:
    print("error")

Dado que theheadofabroom (la persona que publicó la pregunta) había dado algunos comentarios sobre su pregunta original, fui y trabajé en una nueva solución basada en sus comentarios (aún conservé mi respuesta anterior porque creo que puede ser útil para algunos, aunque no 100% exactamente lo que el jefe de cabeza está preguntando). Aquí está la respuesta actualizada:

Aquí está el código para copiarlo y pegarlo :)

#/usr/bin/env python
from functools import wraps 

def singleton(cls):
    instances = {}
    def getinstance(anyArgs=None):
        if cls not in instances:
            instances[cls] = cls(anyArgs)
            return instances[cls]
    return getinstance

def add_function(cls):
    def outer_decorate_it(somefunction):
        @wraps(somefunction) 
        def wrapper( *args, **kwargs): 
            return somefunction(*args, **kwargs)
        setattr(cls, somefunction.__name__, wrapper)
        return somefunction
    return outer_decorate_it

@singleton
class MyClass():
    def __init__(self,count=None):
        print("print argument if any exists",count)

@add_function(MyClass)
def testit():
    print("It's me the function from the class")


MyClass.testit()

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, me gustaría ir 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 importación de un archivo, Python no vuelve a ejecutar el código.


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.


Es ligeramente similar a la respuesta de fab pero no es exactamente la misma.

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 como creado solo una vez? "Spoofing", el constructor podría decirse que perjudica la legibilidad.

Así que mi sugerencia es sólo 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 descarta el uso del constructor o el campo instancepor código de usuario:

if Elvis() is King.instance:

... si está seguro de que Elvisaún no se ha creado y de que se Kingha creado .

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

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

Para completar esto, también puede anular la __delattr__()creación de una Excepción si se intenta eliminar instancey anularla __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. Mientras 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 hace una subclase del objeto en python2.x obtendrá una clase de estilo antiguo, que no usa __new__
  2. Al decorar __new__, debe decorar con @classmethod o __new__será un método de instancia independiente.
  3. Esto posiblemente podría mejorarse mediante el uso de una metaclase, ya que esto le permitiría hacer theuna propiedad de nivel de clase, posiblemente cambiándole el nombre ainstance

Esta solución causa cierta contaminación en el 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 perezosa), 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 romper la inicialización y la instancia estática en

Inicialización impaciente:

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 impaciente:

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

No recuerdo dónde encontré esta solución, pero me parece 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 clases meta, sin herencia múltiple ... y si decide que ya no quiere que sea un Singleton, simplemente elimine el __new__método. Como soy nuevo en Python (y OOP en general) espero que alguien me explique por qué este es un enfoque terrible.


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. Crear 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 una instancia fácilmente, reemplaza el __new__uso de un método definido hace un momento.
  4. La función devuelve 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 una clase decorada.

  5. Si __new__()devuelve una instancia de cls, el __init__()método de la nueva instancia se invocará como __init__(self[, ...]), donde self es la nueva instancia y los argumentos restantes son los mismos que se pasaron __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


Esta respuesta probablemente no sea lo que estás buscando. Quería un singleton en el sentido de que solo ese objeto tenía su identidad, para comparación. En mi caso, estaba siendo utilizado como un valor de Sentinel . Para lo cual la respuesta es muy simple, haga cualquier objeto mything = object()y por la 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

class Foo(object):
     pass

some_global_variable = Foo()

Los módulos se importan solo una vez, todo lo demás se está pensando demasiado. No uses singletons y trata de no usar globales.





metaclass