Criando um singleton em Python


946

Esta pergunta não é para discutir se o padrão de design singleton é desejável ou não , é um anti-padrão ou para qualquer guerra religiosa, mas para discutir como esse padrão é melhor implementado no Python da maneira mais pitônica. Nesse caso, defino "mais pitonico" para significar que segue o "princípio de menor espanto" .

Eu tenho várias classes que se tornariam singletons (meu caso de uso é para um criador de logs, mas isso não é importante). Eu não desejo desordenar várias classes com um valor acrescentado quando posso simplesmente herdar ou decorar.

Melhores métodos:


Método 1: Um 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

Prós

  • Decoradores são aditivos de uma maneira que muitas vezes é mais intuitiva do que a herança múltipla.

Contras

  • Embora os objetos criados usando MyClass () sejam verdadeiros objetos singleton, o MyClass em si é uma função, não uma classe; portanto, você não pode chamar métodos de classe a partir dele. Também para m = MyClass(); n = MyClass(); o = type(n)();entãom == n && m != o && n != o

Método 2: uma classe 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

Prós

  • É uma verdadeira aula

Contras

  • Herança múltipla - eugh! __new__poderia ser substituído durante a herança de uma segunda classe base? É preciso pensar mais do que o necessário.

Método 3: uma metaclasse

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

Prós

  • É uma verdadeira aula
  • Abrange automaticamente a herança
  • Usa __metaclass__para o seu devido propósito (e me conscientizou disso)

Contras

  • Há alguns?

Método 4: decorador retornando uma classe com o mesmo nome

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

Prós

  • É uma verdadeira aula
  • Abrange automaticamente a herança

Contras

  • Não existe uma sobrecarga para criar cada nova classe? Aqui estamos criando duas classes para cada classe que desejamos criar um singleton. Embora isso esteja bem no meu caso, eu me preocupo com o fato de que isso não pode ser dimensionado. É claro que há uma questão de debate sobre se será muito fácil escalar esse padrão ...
  • Qual é o objetivo do _sealedatributo
  • Não é possível chamar métodos com o mesmo nome nas classes base, super()pois elas serão recursivas. Isso significa que você não pode personalizar __new__e não pode subclassificar uma classe que precisa ser chamada __init__.

Método 5: um módulo

um arquivo de módulo singleton.py

Prós

  • Simples é melhor que complexo

Contras


12
Outras três técnicas: use um módulo (geralmente - geralmente, eu acho - esse é um padrão mais apropriado para o Python, mas depende um pouco do que você está fazendo com ele); faça uma única instância e lide com ela ( foo.xou se você insistir em Foo.xvez de Foo().x); use atributos de classe e métodos estáticos / de classe ( Foo.x).
22411 Chris Morgan

10
@ ChrisMorgan: Se você vai usar apenas métodos estáticos / de classe, não se preocupe em criar uma classe.
Cat Plus Plus

2
@ Cat: O efeito é semelhante, no entanto, as razões por trás da criação de uma variável global podem ser praticamente qualquer coisa, incluindo não conhecer melhor. Por que alguém cria um singleton? Se você tiver que perguntar, não deveria estar aqui. Essa explicitação não é apenas mais pitônica, mas torna a manutenção muito mais simples. Sim, os singletons são açúcar sintático para os globais, mas as aulas são açúcar sintático para um monte de coisas feias e eu não acho que alguém lhe diga que você está sempre melhor sem eles.
theheadofabroom

14
@BiggAl: Singletons não são Pythonic, não importa como você os implemente. Eles são um sinal de um design defeituoso, na melhor das hipóteses.
Cat Plus Plus

8
O sentimento anti-signletons é a programação do culto à carga na pior das hipóteses. O mesmo acontece com as pessoas que ouvem (poucas se preocupam em ler) a "declaração Goto considerada prejudicial" e pensam que gotos são um sinal de código incorreto, independentemente do contexto.
Hejazzman

Respostas:


658

Use uma metaclasse

Eu recomendaria o método 2 , mas é melhor usar uma metaclasse do que uma classe base. Aqui está uma implementação de exemplo:

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

Ou no Python3

class Logger(metaclass=Singleton):
    pass

Se você deseja executar __init__sempre que a classe é chamada, adicione

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

para a ifdeclaração em Singleton.__call__.

Algumas palavras sobre metaclasses. Uma metaclasse é a classe de uma classe ; isto é, uma classe é uma instância de sua metaclasse . Você encontra a metaclasse de um objeto no Python com type(obj). As classes normais de novo estilo são do tipo type. Loggerno código acima será do tipo class 'your_module.Singleton', assim como a instância (única) de Loggerserá do tipo class 'your_module.Logger'. Quando você chama logger com Logger(), Python primeiro pede a metaclasse de Logger, Singleton, o que fazer, o que permite a criação da instância a ser antecipadas. Esse processo é o mesmo que o Python perguntando a uma classe o que fazer chamando __getattr__quando você faz referência a um de seus atributos myclass.attribute.

Uma metaclasse decide essencialmente o que significa a definição de uma classe e como implementá-la. Veja, por exemplo , http://code.activestate.com/recipes/498149/ , que recria essencialmente structs de estilo C em Python usando metaclasses. O encadeamento Quais são alguns casos de uso (concretos) para metaclasses? também fornece alguns exemplos, eles geralmente parecem estar relacionados à programação declarativa, especialmente como usada nos ORMs.

Nessa situação, se você usar o Método 2 , e uma subclasse definir um __new__método, ele será executado toda vez que você chamar SubClassOfSingleton()- porque é responsável por chamar o método que retorna a instância armazenada. Com uma metaclasse, ela será chamada apenas uma vez , quando a única instância for criada. Você deseja personalizar o que significa chamar a classe , que é decidida por seu tipo.

Em geral, faz sentido usar uma metaclasse para implementar um singleton. Um singleton é especial porque é criado apenas uma vez e uma metaclasse é a maneira como você personaliza a criação de uma classe . O uso de uma metaclasse fornece mais controle caso você precise personalizar as definições de classe singleton de outras maneiras.

Seus singletons não precisarão de herança múltipla (porque a metaclasse não é uma classe base), mas para subclasses da classe criada que usam herança múltipla, é necessário garantir que a classe singleton seja a primeira / mais à esquerda com uma metaclasse que redefine __call__É muito improvável que seja um problema. O dict da instância não está no espaço de nomes da instância, portanto não o substituirá acidentalmente.

Você também ouvirá que o padrão singleton viola o "Princípio da responsabilidade única" - cada classe deve fazer apenas uma coisa . Dessa forma, você não precisa se preocupar em estragar uma coisa que o código faz se precisar alterar outra, porque elas são separadas e encapsuladas. A implementação da metaclasse passa nesse teste . A metaclasse é responsável por impor o padrão e a classe e subclasses criadas não precisam estar cientes de que são singletons . O método nº 1 falha neste teste, como você observou em "MyClass em si é uma função, não uma classe, portanto você não pode chamar métodos de classe a partir dele".

Versão compatível com Python 2 e 3

Escrever algo que funcione tanto no Python2 quanto no 3 requer o uso de um esquema um pouco mais complicado. Desde metaclasses são geralmente subclasses do tipo type, é possível usar um para criar dinamicamente uma classe base intermediário em tempo de execução com ele como seu metaclass e depois usar isso como os baseclass do público Singletonclasse base. É mais difícil de explicar do que fazer, como ilustrado a seguir:

# 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

Um aspecto irônico dessa abordagem é que ela está usando subclasses para implementar uma metaclasse. Uma vantagem possível é que, ao contrário de uma metaclasse pura, isinstance(inst, Singleton)retornará True.

Correções

Em outro tópico, você provavelmente já percebeu isso, mas a implementação da classe base na sua postagem original está errada. _instancesprecisa ser referenciado na classe , você precisa usar super()ou está recorrendo e __new__é realmente um método estático ao qual você deve passar a classe , não um método de classe, pois a classe real ainda não foi criada quando é chamado. Todas essas coisas também serão verdadeiras para uma implementação de metaclasse.

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 retornando uma aula

Originalmente, eu estava escrevendo um comentário, mas era muito longo, então vou adicioná-lo aqui. O método 4 é melhor que a outra versão do decorador, mas é mais código do que o necessário para um singleton e não é tão claro o que faz.

Os principais problemas decorrem da classe, sendo sua própria classe base. Primeiro, não é estranho ter uma classe como subclasse de uma classe quase idêntica com o mesmo nome que existe apenas em seu __class__atributo? Isto também significa que você não pode definir os métodos que chamam o método de mesmo nome em sua classe base com super()porque irá recorrer. Isso significa que sua classe não pode personalizar __new__e não pode derivar de nenhuma classe que precise ser __init__chamada.

Quando usar o padrão singleton

Seu caso de uso é um dos melhores exemplos de como usar um singleton. Você diz em um dos comentários: "Para mim, o registro sempre pareceu um candidato natural para os singletons". Você está absolutamente certo .

Quando as pessoas dizem que singletons são ruins, o motivo mais comum é que eles são um estado compartilhado implícito . Embora as variáveis ​​globais e as importações de módulos de nível superior sejam um estado compartilhado explícito , outros objetos que são transmitidos geralmente são instanciados. Este é um bom ponto, com duas exceções .

O primeiro, e mencionado em vários lugares, é quando os singletons são constantes . O uso de constantes globais, especialmente enumerações, é amplamente aceito e considerado sensato porque, não importa o que aconteça , nenhum dos usuários pode atrapalhá-los para qualquer outro usuário . Isto é igualmente verdade para um singleton constante.

A segunda exceção, que é mencionada menos, é o oposto - quando o singleton é apenas um coletor de dados , não uma fonte de dados (direta ou indiretamente). É por isso que os madeireiros se sentem como um uso "natural" dos singletons. Como os vários usuários não estão alterando os registradores de maneira que outros usuários se importem, não há realmente um estado compartilhado . Isso nega o argumento principal contra o padrão singleton e os torna uma escolha razoável devido à sua facilidade de uso para a tarefa.

Aqui está uma citação de http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html :

Agora, há um tipo de Singleton que está OK. É um singleton em que todos os objetos alcançáveis ​​são imutáveis. Se todos os objetos são imutáveis, Singleton não possui um estado global, pois tudo é constante. Mas é tão fácil transformar esse tipo de singleton em um mutável, é uma ladeira muito escorregadia. Portanto, eu também sou contra esses singletons, não porque eles são ruins, mas porque é muito fácil para eles ficarem ruins. (Como uma observação lateral, a enumeração Java é apenas esse tipo de singletons. Contanto que você não coloque estado em sua enumeração, você está bem, então não.)

O outro tipo de Singletons, que são semi-aceitáveis, são aqueles que não afetam a execução do seu código. Eles não têm "efeitos colaterais". O log é um exemplo perfeito. É carregado com Singletons e estado global. É aceitável (pois não o machucará) porque seu aplicativo não se comporta de maneira diferente, independentemente de um determinado criador de logs estar ativado ou não. As informações aqui fluem de uma maneira: do seu aplicativo para o criador de logs. Mesmo que os registradores sejam um estado global, uma vez que nenhuma informação flui dos registradores para o seu aplicativo; eles são aceitáveis. Você ainda deve injetar seu logger se quiser que seu teste afirme que algo está sendo registrado, mas, em geral, os registradores não são prejudiciais, apesar de estarem cheios de estado.


5
Não, singletons nunca são bons. O registro em log pode ser um bom candidato para ser global (por mais terrível que seja), mas certamente não é único.
Cat Plus Plus

11
Veja googletesting.blogspot.com/2008/08/… . Geralmente é anti-singleton (por um bom motivo), mas tem uma boa explicação de por que singletons imutáveis ​​e singletons sem efeitos colaterais não têm os mesmos problemas, se você for cuidadoso. Vou citar um pouco no final do meu post.
agf 23/07

4
Meu problema com singletons é a premissa estúpida de "apenas uma instância". Isso e uma tonelada de problemas de segurança do fio. E dependência escondida. Globais são ruins, e singletons são apenas globais com mais problemas.
Cat Plus Plus

4
@ Cat Existem usos muito bons para singletons. A instanciação lenta dos módulos de hardware (especialmente em aplicativos de thread único) é um deles (mas existem singletons seguros para thread).
Paul Manta

3
@ Alcott __new__em uma metaclasse é quando a classe é nova - quando é definida, não quando a instância seria nova. Chamar a classe ( MyClass()) é a operação que você deseja substituir, não a definição da classe. Se você realmente deseja entender como o Python funciona, a melhor coisa que você pode fazer (além de continuar usando) é ler docs.python.org/reference/datamodel.html . Uma boa referência em metaclasses é eli.thegreenplace.net/2011/08/14/python-metaclasses-by-example . Um bom artigo sobre singletons é a série do blog do google que eu vinculei nesta resposta.
AGF

91
class Foo(object):
     pass

some_global_variable = Foo()

Os módulos são importados apenas uma vez, e todo o resto está pensando demais. Não use singletons e tente não usar globais.


14
por que você disse "não use singletons"? Qualquer razão?
Alcott

3
Isso não funcionará se o singleton precisar ser decapado. Usando o exemplo que você deu:s = some_global_variable; str = pickle.dumps(s); s1 = pickle.loads(str); print s is s1; # False
dividebyzero

4
@dividebyzero: o isoperador testa a igualdade de ponteiros. Eu ficaria surpreso - ao ponto de chamá-lo de bug - se pickle.loadsretornasse uma referência a um objeto preexistente em vez de uma referência a um objeto recém-criado. Portanto, teste se s is s1não diz nada sobre a adequação do uso de módulos como singletons.
Jonas Kölker 17/02

1
@ JonasKölker pickle.loads()já faz isso, por exemplo, para instâncias de boole NoneType. pickle.loads(pickle.dumps(False)) is FalserendimentosTrue
Dan Passaro

2
@ leo-the-manic: ponto justo; no entanto, isso é apenas um efeito colateral do Python internar os objetos True, Falsee None, e não tem nada a ver com o por trás de código pickle.loads. Além disso, é seguro fazer apenas para objetos somente leitura. Se pickle.loadsdevolver uma referência a um objeto modificável já existente - como um módulo - isso seria um bug. (E então eu estou de pé por minha implicação de que exemplo de código de dividebyzero não prova nada.)
Jonas Kolker

69

Use um módulo. É importado apenas uma vez. Defina algumas variáveis ​​globais nele - elas serão 'atributos' de singleton. Adicione algumas funções - os 'métodos' do singleton.


11
Então, o que você acaba fazendo é ... Não é uma aula. Você não pode usá-lo como uma classe, você não pode de base outras classes que nela está, você usa sintaxe de importação, e de repente você perde todos os benefícios da OOP ...
theheadofabroom

16
se você puder basear outras classes nele, talvez não seja um singleton. você poderia criar uma da classe derivada, mas também uma da classe base, mas a classe derivada também é um membro da base e você tem duas da base, qual delas você deve usar?
SingleNegationElimination

Isso não funciona nos módulos. No meu módulo "principal", defino um valor. Eu, então, referencio-o em outro módulo e seu nulo. Suspiro.
Paul Kenjora

1
@PaulKenjora Você deve ter um erro no seu código. Se você definir uma variável global em um módulo, ao acessá-la de outro módulo, ela deverá ter o valor.
warvariuc

Tem o valor, mas quando eu o troco, não é preservado. Acabei usando uma classe com propriedades em um módulo que funciona. Tipos simples como globais não funcionaram para mim (eles perderam valores assim que o escopo mudou).
precisa saber é o seguinte

29

Você provavelmente nunca precisará de um singleton em Python. Basta definir todos os seus dados e funções em um módulo e você terá um singleton de fato.

Se você realmente tem que ter uma classe singleton, então eu diria:

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

my_singleton = My_Singleton()

Usar:

from mysingleton import my_singleton
my_singleton.foo()

onde mysingleton.py é o seu nome de arquivo em que My_Singleton está definido. Isso funciona porque após a primeira vez que um arquivo é importado, o Python não executa novamente o código.


4
Principalmente verdade, mas às vezes isso não é suficiente. Por exemplo, eu tenho um projeto com a necessidade de registrar instanciações de muitas classes no nível DEBUG. Preciso de opções de linha de comando analisadas na inicialização para definir o nível de log especificado pelo usuário antes que essas classes sejam instanciadas. As instanciações no nível do módulo tornam isso problemático. É possível que eu estruture cuidadosamente o aplicativo para que todas essas classes não sejam importadas até que o processamento da CLI seja concluído, mas a estrutura natural do meu aplicativo é mais importante do que a adesão dogmática a "singletons ruins", pois eles podem ser feito de forma bastante limpa.
CryingCyclops

se você testasse seu código enquanto corrigia meu_singleton, isso seria possível? pois esse my_singleton pode ser instanciado em algum outro módulo.
Naveen

@Naveen - my_singleton é um único objeto. Se você "corrigi-lo", essa alteração afetará todas as referências futuras, mesmo em outros módulos.
Alan Dyke

16

Aqui está uma lista para você:

singleton = lambda c: c()

Veja como você o usa:

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

assert wat.get_x() == 1

Seu objeto é instanciado ansiosamente. Isso pode ou não ser o que você deseja.


5
-1. Isso é muito ruim e bobo. Você não define uma classe para usar como objeto. Você não pode mais acessar a classe sem ser muito feio type(wat)ou wat.__class__. Se você realmente deseja conseguir isso, defina melhor a classe e instancie-a imediatamente, sem necessidade de mexer com o decorador.
0xc0de 16/10

2
Por que você precisa saber usar a classe de um singleton? Basta usar o objeto singleton ..
Tolli 17/10

1
Não é um padrão singleton ; portanto, na função IMO, o nome deve ser diferente.
GingerPlusPlus

8
Wikipedia: "o padrão singleton é um padrão de design que restringe a instanciação de uma classe a um objeto". Eu diria que minha solução faz exatamente isso. Ok, eu acho que alguém poderia fazer wat2 = type(wat)(), mas isso é python, todos nós concordamos com adultos e tudo mais. Você não pode garantir que haverá apenas uma instância, mas pode garantir que, se as pessoas fizerem uma segunda, ela parecerá feia e - se forem pessoas decentes e honestas - será um sinal de alerta para elas. o que estou perdendo?
Jonas Kölker

7

Confira a pergunta sobre estouro de pilha. Existe uma maneira simples e elegante de definir singletons em Python? com várias soluções.

Eu recomendo fortemente assistir às palestras de Alex Martelli sobre padrões de design em python: parte 1 e parte 2 . Em particular, na parte 1, ele fala sobre singletons / objetos de estado compartilhado.


2
Embora isso não seja realmente uma resposta para minha pergunta, os recursos para os quais você aponta são muito úteis.
Relutantemente

3

Aqui está minha própria implementação de singletons. Tudo que você precisa fazer é decorar a classe; para obter o singleton, você precisará usar o Instancemétodo Aqui está um exemplo:

   @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

E aqui está o 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
Não é verdadeiro singleton. SingletonList = Singleton(list).Instance(); print(SingletonList is type(SingletonList)())deve imprimir Trueem singleton verdadeiro; com suas impressões de códigoFalse
GingerPlusPlus

@GingerPlusPlus Eu estava ciente de algumas limitações, mas não da que você apontou. Obrigado por mencionar. Infelizmente, não tenho tempo no momento para diminuir uma solução para isso.
Paul Manta

2

O método 3 parece ser muito interessante, mas se você deseja que seu programa seja executado no Python 2 e no Python 3 , ele não funciona. Mesmo a proteção de variantes separadas com testes para a versão Python falha, porque a versão Python 3 gera um erro de sintaxe no Python 2.

Agradecimentos a Mike Watkins: http://mikewatkins.ca/2008/11/29/python-2-and-3-metaclasses/ . Se você deseja que o programa funcione no Python 2 e Python 3, é necessário fazer 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

Presumo que 'objeto' na atribuição precise ser substituído pelo 'BaseClass', mas ainda não tentei (tentei o código conforme ilustrado).


Certamente esta não é uma metaclasse - em python3 usar um metaclass para construir MyClass você fariaclass MyClass(metaclass=Singleton)
theheadofabroom

O link mikewatkins.ca está (efetivamente) quebrado.
Peter Mortensen

1

Bem, além de concordar com a sugestão geral Pythonic de ter um nível global de módulo, que tal isso:

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)

A saída é:

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

Qual é o objetivo do _sealedatributo? Tanto quanto vejo isso não faz nada? Algo está me incomodando com isso que diz que não deve ter um bom desempenho ... Farei alguns testes comparativos ainda esta semana.
theheadofabroom

_sealed garante que seu init seja executado apenas uma vez; Não vejo qualquer ponto porque deve um desempenho pior do que a normal função semelhante decorador - a função é executada apenas uma vez por classe, e retorna uma nova classe herdada
Guarda

BTW, sua edição contém guias que quebram os recuos Você também diz 'estamos criando 2 classes' - você quer dizer que estamos criando '1 classe extra'?
Guarda

Sim, uma aula extra é o que eu quis dizer. Pretendo incluir coisas __init__para serem chamadas sempre que forem inicializadas. Apenas um simples 'Sendo inicializado no class.method'. como por recuo - você usou tabulações e espaços - eu fixo a maior parte dele, mas parece ter perdido um se você quiser obtê-lo (basta verificar o registro de edição)
theheadofabroom

re init : claro que depende de você, apenas tentei imitar o comportamento singleton em outras linguagens, onde o código do construtor (que não é exatamente init , mas muito próximo em seu sentido) é chamado apenas uma vez se você quiser que o init seja chamado todas as vezes, basta matar todas as referências a _sealed re spaces / tabs - bem, então o meu emacs precisa ser corrigido. de qualquer maneira, acima é a versão corrigida
Guarda

1

Que tal agora:

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

Use-o como decorador em uma classe que deve ser um singleton. Como isso:

@singleton
class MySingleton:
    #....

Isso é semelhante ao singleton = lambda c: c()decorador em outra resposta. Como a outra solução, a única instância tem nome da classe ( MySingleton). No entanto, com esta solução, você ainda pode "criar" instâncias (na verdade, obter a única instância) da classe, fazendo isso MySingleton(). Isso também impede que você crie instâncias adicionais fazendo type(MySingleton)()isso (que também retorna a mesma instância).


Você não define uma classe para usá-la como um objeto.
0xc0de 16/10

1
Toda vez que você liga type(MySingleton)(), MySingleton.__init__()é chamado e o objeto é inicializado várias vezes; você pode consertá-lo escrevendo cls.__init__ = lambda self: passno seu singleton. Além disso, a substituição cls.__call__parece inútil e até prejudicial - __call__definida neste contexto é usada quando você liga MySingleton(any, list, of, arguments), não quando liga type(MySingleton)(any, list, of, arguments).
GingerPlusPlus

@GingerPlusPlus, Obrigado por apontar que __init__()é chamado novamente quando o faz type(MySingleton)(). A solução que você propôs (adicionando cls.__init__ = lambda self: pass) fornece um erro de sintaxe, porque a última parte da expressão lambda precisa ser uma expressão, não uma instrução. No entanto, adicionar cls.__init__ = lambda self: Noneobras, então eu adicionei isso à minha resposta.
Tolli

1
@GingerPlusPlus, em relação ao uso de __call__. minha intenção era fazer os dois type(MySingleton)()e MySingleton()retornar a instância. Então, está fazendo o que eu queria. Você pode pensar em MySingleton como o tipo do singleton ou a instância do singleton (ou ambos).
Tolli

1

Vou jogar o meu no ringue. É um decorador simples.

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.

Benefícios que eu acho que tem sobre algumas das outras soluções:

  • É claro e conciso (aos meus olhos; D).
  • Sua ação é completamente encapsulada. Você não precisa mudar nada sobre a implementação do YourClass. Isso inclui a não necessidade de usar uma metaclasse para sua classe (observe que a metaclasse acima está na fábrica, não na classe "real").
  • Não depende de nada que remenda macacos.
  • É transparente para os chamadores:
    • Os chamadores ainda simplesmente importam YourClass, parece uma classe (porque é) e a usam normalmente. Não há necessidade de adaptar os chamadores a uma função de fábrica.
    • O que YourClass()instancia ainda é uma instância verdadeira do que YourClassvocê implementou, não um proxy de qualquer tipo, portanto não há chance de efeitos colaterais resultantes disso.
    • isinstance(instance, YourClass) e operações similares ainda funcionam conforme o esperado (embora esse bit exija abc, exclua o Python <2.6).

Ocorre-me uma desvantagem: os métodos de classe e os métodos estáticos da classe real não podem ser chamados de forma transparente pela classe de fábrica que a oculta. Eu usei isso raramente o suficiente para nunca ter encontrado essa necessidade, mas isso seria facilmente corrigido usando uma metaclasse personalizada na fábrica que implementa __getattr__()para delegar o acesso all-ish de atributo à classe real.

Um padrão relacionado que eu realmente achei mais útil (não que eu esteja dizendo que esse tipo de coisa é necessário com muita frequência) é um padrão "Único", em que instanciar a classe com os mesmos argumentos resulta em voltar à mesma instância. Ou seja, um "singleton por argumentos". O acima se adapta a esse poço e se torna ainda mais 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

Dito isso, concordo com o conselho geral de que, se você acha que precisa de uma dessas coisas, provavelmente deve parar por um momento e se perguntar se realmente precisa. 99% do tempo, YAGNI.


1
  • Se alguém quiser ter vários números de instâncias da mesma classe, mas apenas se os argumentos ou argumentos forem diferentes, pode-se usar isso
  • Ex.
    1. Se você possui uma serialcomunicação de manipulação de classe e, para criar uma instância, deseja enviar a porta serial como argumento, a abordagem tradicional não funcionará
    2. Usando os decoradores acima mencionados, é possível criar várias instâncias da classe se os argumentos forem diferentes.
    3. Para os mesmos argumentos, o decorador retornará a mesma instância que já foi criada.
>>> 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 baseado na resposta 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)

Explicação:

  1. Crie uma nova classe, herdada da dada cls
    (não é modificada clscaso alguém queira, por exemplo singleton(list))

  2. Crie instância. Antes de substituir __new__, é tão fácil.

  3. Agora, quando criamos uma instância facilmente, substitui o __new__método definido momento atrás.
  4. A função retorna instanceapenas quando é o que o chamador espera; caso contrário, aumenta TypeError.
    A condição não é atendida quando alguém tenta herdar da classe decorada.

  5. Se __new__()retornar uma instância de cls, o __init__()método da nova instância será chamado como __init__(self[, ...]), em que self é a nova instância e os argumentos restantes serão os mesmos para os quais foram passados __new__().

    instancejá foi inicializado, portanto, a função substitui __init__a função sem fazer nada.

Veja trabalhando on-line


0

É um pouco semelhante à resposta de fab, mas não é exatamente a mesma.

O contrato singleton não exige que possamos chamar o construtor várias vezes. Como um singleton deve ser criado uma vez e apenas uma vez, não deve ser visto como criado apenas uma vez? "Falsificar" o construtor prejudica a legibilidade.

Então, minha sugestão é apenas esta:

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

Isso não descarta o uso do construtor ou do campo instancepelo código do usuário:

if Elvis() is King.instance:

... se você tiver certeza de que Elvisainda não foi criado, e isso Kingfoi.

Mas incentiva os usuários a usar o themétodo universalmente:

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

Para fazer isso completo, você também pode substituir __delattr__()para gerar uma Exceção se for feita uma tentativa de exclusão instancee substituir para gerar __del__()uma Exceção (a menos que saibamos que o programa está terminando ...)

Melhorias adicionais


Agradeço aos que ajudaram com comentários e edições, dos quais mais são bem-vindos. Enquanto eu uso o Jython, isso deve funcionar de maneira mais geral e ser seguro para threads.

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

Pontos de observação:

  1. Se você não subclassificar do objeto no python2.x, receberá uma classe de estilo antigo, que não usa __new__
  2. Ao decorar, __new__você deve decorar com @classmethod ou __new__será um método de instância independente
  3. Isso poderia ser melhorado com o uso de uma metaclasse, pois isso permitiria criar theuma propriedade em nível de classe, possivelmente renomeá-la parainstance

Embora essa seja uma interpretação um pouco diferente do padrão singleton, tenho certeza de que ainda é válido, embora eu possa ser tentado a usar __new__ em vez de __init__, pois atua puramente nos atributos de classe e isso impede que exista brevemente uma segunda instância. A diferença então entre isso e o método 2 é se a tentativa de inicializar mais de uma vez retorna a instância única ou gera uma exceção. Acho que estou feliz por satisfazer o padrão de singleton, um é mais fácil de usar, enquanto o outro é mais explícito que é um singleton.
theheadofabroom 21/02

Obviamente uso do nome da classe no __init__impede subclassificação, mas enquanto isso torna as coisas mais fáceis, não é necessário
theheadofabroom

Obrigado ... ah sim, uma segunda instância momentânea antes que a exceção seja lançada. Eu modifiquei o __init__para que, esperançosamente, isso deva ser subclassável ...
mike

Cool, theprovavelmente poderia beneficiar de ser um método de classe por razões semelhantes
theheadofabroom

sim, você está certo. Então você pode ter um singleton da subclasse SuperElvis e (por exemplo) um singleton da subclasse ImaginaryElvis ... e eles podem coexistir. Veja pensamentos adicionais. Por favor, sinta-se livre para melhorar meu código.
microfone roedor

0

Um forro (não tenho orgulho, mas faz o trabalho):

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

Isso pode fazer o trabalho, desde que sua classe não foi importado para quaisquer outros módulos ...
Aran-Fey

Verdade. Se você puder pagar o custo de iniciar a classe, poderá executar um Myclass () imediatamente após a definição da classe para evitar isso.
Polvoazul 26/10/19

0

Se você não precisar de uma inicialização lenta da instância do Singleton, o seguinte deve ser fácil e seguro para threads:

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

Dessa maneira, Aé um singleton inicializado na importação do módulo.


0

Talvez eu não entenda o padrão de singleton, mas minha solução é simples e pragmática (pitônica?). Esse código cumpre dois objetivos

  1. Torne a instância de Fooacessível em qualquer lugar (global).
  2. Somente uma instância de Foopode existir.

Este é o 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()

Resultado

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

Depois de ter lutado com isso por algum tempo, acabei criando o seguinte, para que o objeto de configuração fosse carregado apenas uma vez, quando chamado de módulos separados. A metaclasse permite que uma instância de classe global seja armazenada no ditado interno, que atualmente parece ser a maneira mais clara de armazenar um programa global adequado.

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

Não me lembro onde encontrei essa solução, mas acho que ela é a mais 'elegante' do meu ponto de vista não-especialista em 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 que eu gosto disso? Sem decoradores, sem meta classes, sem herança múltipla ... e se você decidir que não quer mais ser um Singleton, exclua o __new__método. Como eu sou novo no Python (e OOP em geral), espero que alguém me indique por que essa é uma abordagem terrível?


2
por que essa é uma abordagem terrível? quando você deseja criar outra classe singleton, você deve copiar e colar o __new__. Não se repita .
GingerPlusPlus

Além disso, por que suas novas tomadas *argse **kwargs, e depois não fazem nada com elas? Passá-las para dict.__new__desta forma: dict.__new__(cls, *args, **kwargs).
GingerPlusPlus

Isso chamará o __init__método toda vez que a classe for chamada. Se o seu __init__método realmente fez alguma coisa, você perceberia o problema. Sempre que você faz SomeSingleton(), o estado do seu singleton é redefinido pelo __init__método.
`` #:

-2

Esta é a minha maneira 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
Isso não é estritamente um singleton, pois permite que mais de uma instância da classe exista. Uma melhoria seria fazer a classe incapaz de ser inicializado, e têm todos os métodos de classe agir sobre atributos de classe
theheadofabroom

-2

Essa resposta provavelmente não é o que você está procurando. Eu queria um singleton no sentido de que apenas esse objeto tinha sua identidade, para comparação. No meu caso, estava sendo usado como um valor do Sentinel . Para a qual a resposta é muito simples, faça qualquer objeto mything = object()e, pela natureza do python, apenas essa coisa terá sua identidade.

#!python
MyNone = object()  # The singleton

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

Aprendi que os módulos podem realmente ser importados várias vezes; nesse caso, é apenas um singleton local, que não é realmente um singleton em nenhuma capacidade.
ThorSummoner 23/03

Você pode explicar como um módulo pode ser importado várias vezes? A única vez que vi que é quando ocorre uma exceção enquanto o módulo está carregado, o usuário ainda pode carregar o módulo mais tarde, mas os efeitos colaterais já ocorreram, portanto, algumas ações podem ser executadas uma segunda vez.
sleblanc 16/03/19

Depois que um módulo é totalmente carregado, não vejo uma maneira de executá-lo novamente, a não ser forçando o intérprete a fazê-lo usando evalor importlib.reload.
sleblanc 16/03/19

-3

Essa solução causa alguma poluição no namespace no nível do módulo (três definições em vez de apenas uma), mas acho fácil de seguir.

Eu gostaria de poder escrever algo assim (inicialização lenta), mas infelizmente as classes não estão disponíveis no corpo de suas próprias definições.

# 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 isso não é possível, podemos interromper a inicialização e a instância estática em

Inicialização 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

Inicialização lenta:

Inicialização 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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.