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 if
declaraçã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
. Logger
no código acima será do tipo class 'your_module.Singleton'
, assim como a instância (única) de Logger
será 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 struct
s 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 Singleton
classe 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. _instances
precisa 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.
foo.x
ou se você insistir emFoo.x
vez deFoo().x
); use atributos de classe e métodos estáticos / de classe (Foo.x
).