¿Hay una manera simple y elegante de definir singletons? [duplicar]


Esta pregunta ya tiene una respuesta aquí:

Parece que hay muchas maneras de definir singletons en Python. ¿Existe una opinión consensuada sobre el desbordamiento de pila?

Author: martineau, 2008-08-28

22 answers

Realmente no veo la necesidad, ya que un módulo con funciones (y no una clase) serviría bien como un singleton. Todas sus variables estarían enlazadas al módulo, el cual no podría ser instanciado repetidamente de todos modos.

Si desea usar una clase, no hay forma de crear clases privadas o constructores privados en Python, por lo que no puede protegerse contra múltiples instanciaciones, que no sea solo a través de la convención en el uso de su API. Todavía pondría métodos en un módulo, y consideraría el módulo como el singleton.

 316
Author: Staale,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-13 20:44:16

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 Instance. He aquí 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. Also, the decorated class cannot be
    inherited from. 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.

    """

    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)
 273
Author: Paul Manta,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-05 19:57:19

Puedes anular el método __new__ así:

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


if __name__ == '__main__':
    s1 = Singleton()
    s2 = Singleton()
    if (id(s1) == id(s2)):
        print "Same"
    else:
        print "Different"
 174
Author: jojo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-12 01:59:28

Un enfoque ligeramente diferente para implementar el singleton en Python es el patrón borg por Alex Martelli (empleado de Google y genio de Python).

class Borg:
    __shared_state = {}
    def __init__(self):
        self.__dict__ = self.__shared_state

Así que en lugar de forzar a todas las instancias a tener la misma identidad, comparten estado.

 104
Author: Peter Hoffmann,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-13 20:46:02

El enfoque de módulo funciona bien. Si absolutamente necesito un singleton prefiero el enfoque Metaclass.

class Singleton(type):
    def __init__(cls, name, bases, dict):
        super(Singleton, cls).__init__(name, bases, dict)
        cls.instance = None 

    def __call__(cls,*args,**kw):
        if cls.instance is None:
            cls.instance = super(Singleton, cls).__call__(*args, **kw)
        return cls.instance

class MyClass(object):
    __metaclass__ = Singleton
 75
Author: Acuminate,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-13 02:50:29

Vea esta implementación desde PEP318 , implementando el patrón singleton con un decorador:

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

@singleton
class MyClass:
    ...
 41
Author: Wei,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-14 21:21:09

Como dice la respuesta aceptada , la forma más idiomática es simplemente usar un módulo.

Con eso en mente, aquí hay una prueba de concepto:

def singleton(cls):
    obj = cls()
    # Always return the same object
    cls.__new__ = staticmethod(lambda cls: obj)
    # Disable __init__
    try:
        del cls.__init__
    except AttributeError:
        pass
    return cls

Vea el modelo de datos de Python para más detalles sobre __new__.

Ejemplo:

@singleton
class Duck(object):
    pass

if Duck() is Duck():
    print "It works!"
else:
    print "It doesn't work!"

Notas:

  1. Tienes que usar clases de estilo nuevo (derivar de object) para esto.

  2. El singleton se inicializa cuando se define, en lugar de la primera vez que es utilizar.

  3. Esto es solo un ejemplo de juguete. Nunca he usado esto en código de producción, y no planeo hacerlo.

 26
Author: Lambda Fairy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:33:24

La única vez que escribí un singleton en Python utilicé una clase donde todas las funciones miembro tenían el decorador classmethod.

class foo:
  x = 1

  @classmethod
  def increment(cls, y = 1):
    cls.x += y
 14
Author: David Locke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-04-21 18:44:06

La documentación de Python cubre esto:

class Singleton(object):
    def __new__(cls, *args, **kwds):
        it = cls.__dict__.get("__it__")
        if it is not None:
            return it
        cls.__it__ = it = object.__new__(cls)
        it.init(*args, **kwds)
        return it
    def init(self, *args, **kwds):
        pass

Probablemente lo reescribiría para que se vea más como esto:

class Singleton(object):
    """Use to create a singleton"""
    def __new__(cls, *args, **kwds):
        """
        >>> s = Singleton()
        >>> p = Singleton()
        >>> id(s) == id(p)
        True
        """
        self = "__self__"
        if not hasattr(cls, self):
            instance = object.__new__(cls)
            instance.init(*args, **kwds)
            setattr(cls, self, instance)
        return getattr(cls, self)

    def init(self, *args, **kwds):
        pass

Debería ser relativamente limpio extender esto:

class Bus(Singleton):
    def init(self, label=None, *args, **kwds):
        self.label = label
        self.channels = [Channel("system"), Channel("app")]
        ...
 13
Author: Brian Bruggeman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-14 21:26:51

Estoy muy inseguro sobre esto, pero mi proyecto usa ' singletons de convención '( no singletons forzados), es decir, si tengo una clase llamada DataController, defino esto en el mismo módulo:

_data_controller = None
def GetDataController():
    global _data_controller
    if _data_controller is None:
        _data_controller = DataController()
    return _data_controller

No es elegante, ya que es un completo de seis líneas. Pero todos mis singletons usan este patrón, y es al menos muy explícito (que es pitónico).

 12
Author: u0b34a0f6ae,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-20 19:56:46

También hay algunos artículos interesantes en el blog de pruebas de Google, discutiendo por qué singleton son / pueden ser malos y son un anti-patrón:

 9
Author: FrankS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-01 09:24:09

Crear un decorador singleton (también conocido como una anotación) es una forma elegante si desea decorar (anotar) las clases en el futuro. A continuación, solo tienes que poner @singleton antes de la definición de la clase.

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

@singleton
class MyClass:
    ...
 9
Author: Matt Alcock,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-02-22 22:16:50

Aquí hay un ejemplo de Python IAQ de Peter Norvig ¿Cómo hago el Patrón Singleton en Python? (Debe usar la función de búsqueda de su navegador para encontrar esta pregunta, no hay enlace directo, lo siento)

También Bruce Eckel tiene otro ejemplo en su libro Thinking in Python (de nuevo no hay un enlace directo al código)

 7
Author: Serge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-28 09:28:49

Creo que forzar que una clase o una instancia sea un singleton es exagerado. Personalmente, me gusta definir una clase instanciable normal, una referencia semiprivada y una función de fábrica simple.

class NothingSpecial:
    pass

_the_one_and_only = None

def TheOneAndOnly():
    global _the_one_and_only
    if not _the_one_and_only:
        _the_one_and_only = NothingSpecial()
    return _the_one_and_only

O si no hay ningún problema con la creación de instancias cuando el módulo se importa por primera vez:

class NothingSpecial:
    pass

THE_ONE_AND_ONLY = NothingSpecial()

De esa manera puede escribir pruebas contra instancias nuevas sin efectos secundarios, y no hay necesidad de rociar el módulo con declaraciones globales, y si es necesario, puede derivar variantes en el futuro.

 5
Author: Mark Evans,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-14 21:23:00

El patrón Singleton implementado con Python cortesía de ActiveState.

Parece que el truco es poner la clase que se supone que solo tiene una instancia dentro de otra clase.

 3
Author: Mark Biek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-03 20:54:25

OK, singleton podría ser bueno o malo, lo sé. Esta es mi implementación, y simplemente extiendo un enfoque clásico para introducir una caché dentro y producir muchas instancias de un tipo diferente o, muchas instancias del mismo tipo, pero con diferentes argumentos.

Lo llamé Singleton_group, porque agrupa instancias similares y evita que se pueda crear un objeto de la misma clase, con los mismos argumentos:

# Peppelinux's cached singleton
class Singleton_group(object):
    __instances_args_dict = {}
    def __new__(cls, *args, **kwargs):
        if not cls.__instances_args_dict.get((cls.__name__, args, str(kwargs))):
            cls.__instances_args_dict[(cls.__name__, args, str(kwargs))] = super(Singleton_group, cls).__new__(cls, *args, **kwargs)
        return cls.__instances_args_dict.get((cls.__name__, args, str(kwargs)))


# It's a dummy real world use example:
class test(Singleton_group):
    def __init__(self, salute):
        self.salute = salute

a = test('bye')
b = test('hi')
c = test('bye')
d = test('hi')
e = test('goodbye')
f = test('goodbye')

id(a)
3070148780L

id(b)
3070148908L

id(c)
3070148780L

b == d
True


b._Singleton_group__instances_args_dict

{('test', ('bye',), '{}'): <__main__.test object at 0xb6fec0ac>,
 ('test', ('goodbye',), '{}'): <__main__.test object at 0xb6fec32c>,
 ('test', ('hi',), '{}'): <__main__.test object at 0xb6fec12c>}

Cada objeto lleva la caché de singleton... Esto podría ser malo, pero funciona muy bien para algunos:)

 3
Author: Mychot sad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-14 21:25:49
class Singleton(object[,...]):

    staticVar1 = None
    staticVar2 = None

    def __init__(self):
        if self.__class__.staticVar1==None :
            # create class instance variable for instantiation of class
            # assign class instance variable values to class static variables
        else:
            # assign class static variable values to class instance variables
 2
Author: John Mee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-26 01:53:38

Mi solución simple que se basa en el valor predeterminado de los parámetros de la función.

def getSystemContext(contextObjList=[]):
    if len( contextObjList ) == 0:
        contextObjList.append( Context() )
        pass
    return contextObjList[0]

class Context(object):
    # Anything you want here
 2
Author: Tiezhen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-28 13:15:24

Siendo relativamente nuevo en Python, no estoy seguro de cuál es el idioma más común, pero lo más simple que se me ocurre es usar un módulo en lugar de una clase. Lo que habrían sido métodos de instancia en su clase se convierten en funciones en el módulo y cualquier dato se convierte en variables en el módulo en lugar de miembros de la clase. Sospecho que este es el enfoque pitónico para resolver el tipo de problema para el que la gente usa singletons.

Si realmente quieres una clase singleton, hay una implementación razonable descrita en el primer golpe en Google para "Python singleton", específicamente:

class Singleton:
    __single = None
    def __init__( self ):
        if Singleton.__single:
            raise Singleton.__single
        Singleton.__single = self

Eso parece hacer el truco.

 2
Author: John,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-13 20:45:18

El medio hermano de Singleton

Estoy completamente de acuerdo con staale y dejo aquí una muestra de la creación de un medio hermano singleton:

class void:pass
a = void();
a.__class__ = Singleton

a se reportará ahora como de la misma clase que singleton incluso si no se parece a ella. Así que los singletons que usan clases complicadas terminan dependiendo de que no nos metamos mucho con ellos.

Siendo así, podemos tener el mismo efecto y usar cosas más simples como una variable o un módulo. Aún así, si queremos usar clases para la claridad y porque en Python una clase es un objeto, por lo que ya tenemos el objeto (no y la instancia, pero hará lo mismo).

class Singleton:
    def __new__(cls): raise AssertionError # Singletons can't have instances

Allí tenemos un buen error de aserción si intentamos crear una instancia, y podemos almacenar en derivaciones miembros estáticos y hacer cambios en ellos en tiempo de ejecución (me encanta Python). Este objeto es tan bueno como otros acerca de medio hermanos (todavía puede crearlos si lo desea), sin embargo, tenderá a correr más rápido debido a la simplicidad.

 2
Author: neu-rah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-14 21:30:33
class Singeltone(type):
    instances = dict()

    def __call__(cls, *args, **kwargs):
        if cls.__name__ not in Singeltone.instances:            
            Singeltone.instances[cls.__name__] = type.__call__(cls, *args, **kwargs)
        return Singeltone.instances[cls.__name__]


class Test(object):
    __metaclass__ = Singeltone


inst0 = Test()
inst1 = Test()
print(id(inst1) == id(inst0))
 1
Author: Volodymyr Pavlenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-15 07:08:55

En los casos en los que no desea la solución basada en metaclase anterior, y no le gusta el enfoque simple basado en decorador de funciones (por ejemplo, porque en ese caso los métodos estáticos en la clase singleton no funcionarán), este compromiso funciona:

class singleton(object):
  """Singleton decorator."""

  def __init__(self, cls):
      self.__dict__['cls'] = cls

  instances = {}

  def __call__(self):
      if self.cls not in self.instances:
          self.instances[self.cls] = self.cls()
      return self.instances[self.cls]

  def __getattr__(self, attr):
      return getattr(self.__dict__['cls'], attr)

  def __setattr__(self, attr, value):
      return setattr(self.__dict__['cls'], attr, value)
 0
Author: ithkuil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-14 21:23:48