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
_sealed
atributo - 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
foo.x
ou se você insistir emFoo.x
vez deFoo().x
); use atributos de classe e métodos estáticos / de classe (Foo.x
).Respostas:
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:
Ou no Python3
Se você deseja executar
__init__
sempre que a classe é chamada, adicionepara a
if
declaração emSingleton.__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 tipotype
.Logger
no código acima será do tipoclass 'your_module.Singleton'
, assim como a instância (única) deLogger
será do tipoclass 'your_module.Logger'
. Quando você chama logger comLogger()
, Python primeiro pede a metaclasse deLogger
,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 atributosmyclass.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ê chamarSubClassOfSingleton()
- 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úblicoSingleton
classe base. É mais difícil de explicar do que fazer, como ilustrado a seguir: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 usarsuper()
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.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 comsuper()
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 :
fonte
__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.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.
fonte
s = some_global_variable; str = pickle.dumps(s); s1 = pickle.loads(str); print s is s1; # False
is
operador testa a igualdade de ponteiros. Eu ficaria surpreso - ao ponto de chamá-lo de bug - sepickle.loads
retornasse uma referência a um objeto preexistente em vez de uma referência a um objeto recém-criado. Portanto, teste ses is s1
não diz nada sobre a adequação do uso de módulos como singletons.pickle.loads()
já faz isso, por exemplo, para instâncias debool
eNoneType
.pickle.loads(pickle.dumps(False)) is False
rendimentosTrue
True
,False
eNone
, e não tem nada a ver com o por trás de códigopickle.loads
. Além disso, é seguro fazer apenas para objetos somente leitura. Sepickle.loads
devolver 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.)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.
fonte
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:
Usar:
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.
fonte
Aqui está uma lista para você:
Veja como você o usa:
Seu objeto é instanciado ansiosamente. Isso pode ou não ser o que você deseja.
fonte
type(wat)
ouwat.__class__
. Se você realmente deseja conseguir isso, defina melhor a classe e instancie-a imediatamente, sem necessidade de mexer com o decorador.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?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.
fonte
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
Instance
método Aqui está um exemplo:E aqui está o código:
fonte
SingletonList = Singleton(list).Instance(); print(SingletonList is type(SingletonList)())
deve imprimirTrue
em singleton verdadeiro; com suas impressões de códigoFalse
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:
Presumo que 'objeto' na atribuição precise ser substituído pelo 'BaseClass', mas ainda não tentei (tentei o código conforme ilustrado).
fonte
class MyClass(metaclass=Singleton)
Bem, além de concordar com a sugestão geral Pythonic de ter um nível global de módulo, que tal isso:
A saída é:
fonte
_sealed
atributo? 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.__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)Que tal agora:
Use-o como decorador em uma classe que deve ser um singleton. Como isso:
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 issoMySingleton()
. Isso também impede que você crie instâncias adicionais fazendotype(MySingleton)()
isso (que também retorna a mesma instância).fonte
type(MySingleton)()
,MySingleton.__init__()
é chamado e o objeto é inicializado várias vezes; você pode consertá-lo escrevendocls.__init__ = lambda self: pass
no seusingleton
. Além disso, a substituiçãocls.__call__
parece inútil e até prejudicial -__call__
definida neste contexto é usada quando você ligaMySingleton(any, list, of, arguments)
, não quando ligatype(MySingleton)(any, list, of, arguments)
.__init__()
é chamado novamente quando o faztype(MySingleton)()
. A solução que você propôs (adicionandocls.__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, adicionarcls.__init__ = lambda self: None
obras, então eu adicionei isso à minha resposta.__call__
. minha intenção era fazer os doistype(MySingleton)()
eMySingleton()
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).Vou jogar o meu no ringue. É um decorador simples.
Benefícios que eu acho que tem sobre algumas das outras soluções:
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").YourClass
, parece uma classe (porque é) e a usam normalmente. Não há necessidade de adaptar os chamadores a uma função de fábrica.YourClass()
instancia ainda é uma instância verdadeira do queYourClass
você 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:
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.
fonte
serial
comunicaçã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áfonte
Código baseado na resposta de Tolli .
Explicação:
Crie uma nova classe, herdada da dada
cls
(não é modificada
cls
caso alguém queira, por exemplosingleton(list)
)Crie instância. Antes de substituir
__new__
, é tão fácil.__new__
método definido momento atrás.A função retorna
instance
apenas quando é o que o chamador espera; caso contrário, aumentaTypeError
.A condição não é atendida quando alguém tenta herdar da classe decorada.
instance
já foi inicializado, portanto, a função substitui__init__
a função sem fazer nada.Veja trabalhando on-line
fonte
É 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:
Isso não descarta o uso do construtor ou do campo
instance
pelo código do usuário:... se você tiver certeza de que
Elvis
ainda não foi criado, e issoKing
foi.Mas incentiva os usuários a usar o
the
método universalmente:Para fazer isso completo, você também pode substituir
__delattr__()
para gerar uma Exceção se for feita uma tentativa de exclusãoinstance
e 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.
Pontos de observação:
__new__
__new__
você deve decorar com @classmethod ou__new__
será um método de instância independentethe
uma propriedade em nível de classe, possivelmente renomeá-la parainstance
fonte
__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.__init__
impede subclassificação, mas enquanto isso torna as coisas mais fáceis, não é necessário__init__
para que, esperançosamente, isso deva ser subclassável ...the
provavelmente poderia beneficiar de ser um método de classe por razões semelhantesUm forro (não tenho orgulho, mas faz o trabalho):
fonte
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:
Dessa maneira,
A
é um singleton inicializado na importação do módulo.fonte
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
Foo
acessível em qualquer lugar (global).Foo
pode existir.Este é o código.
Resultado
fonte
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.
fonte
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:
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?fonte
__new__
. Não se repita .*args
e**kwargs
, e depois não fazem nada com elas? Passá-las paradict.__new__
desta forma:dict.__new__(cls, *args, **kwargs)
.__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ê fazSomeSingleton()
, o estado do seu singleton é redefinido pelo__init__
método.Esta é a minha maneira preferida de implementar singletons:
fonte
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.fonte
eval
orimportlib.reload
.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.
Como isso não é possível, podemos interromper a inicialização e a instância estática em
Inicialização ansiosa:
Inicialização lenta:
Inicialização ansiosa:
fonte