Singleton

Possívelmente o pattern Singleton seja o design pattern mais simples dentre os patterns do GoF. A inteção do pattern singleton consiste em básicamente garantir que uma classe possua somente uma instância durante todo o ciclo de vida de uma aplicação assim como somente um ponto de acesso a essa instância. Um exemplo comum de uso desse pattern seria o de criação de um pool de conexões ou até mesmo um gerenciador de configurações.

Implementação

Como dito anteriormente, o pattern singleton é extremamente simples. Para implementar esse pattern basta criar um mecanismo onde seja possível ter acesso a instância única da classe, de forma que seja desnecessário para o usuário controlar as instâncias dessa classe de forma direta.

Modo tradicional

O modo tradicional de implementação desse pattern se baseia na criação de um método de classe responsável por garatir a existência de somente uma instância dessa classe.

# my_module.py
class Singleton:

    _instance = None

    def __init__(self):
        self.some_attribute = None

    @classmethod
    def instance(cls):
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance

Toda inteligência do exemplo acima fica por conta do classmethod instance(). Esse método é responsável por checar se já existe uma instância da classe Singleton e caso não exista, ele cria essa instância e retorna para o usuário, caso contrário, apenas retorna a instância já existente. O método instance() utiliza o pattern Lazy Initialization visto que a instância da classe será criada somente após o primeiro acesso.

>>> from my_module import Singleton
>>> foo = Singleton.instance()
>>> bar = Singleton.instance()
>>> foo is bar
True
>>> foo.some_attribute = 'some value'
>>> bar.some_attribute
'some value'

Note que nesse exemplo foi posssível validar que ao utilizarmos o método instance() estamos sempre recebendo como retorno a mesma instância da classe Singleton.

Modo simplificado

No GoF os autores são bem enfáticos quanto ao não uso de variáveis globais na implementação do pattern singleton 1, visto que esse mecanismo não impede que sejam criadas diversas instâncias dessa classe, porém, a implementação tradicional do pattern em python também não garante isso. Inclusive essa é uma das razões pela qual alguns programadores python preferem o pattern Borg em comparação ao singleton (além do fato de acharem o pattern singleton desnecessário 2). Sendo assim, o modo mais simples de implementar o pattern singleton em python é a utilização de váriaveis de módulos, visto que os módulos python são carregados somente uma vez 3 (somente na primeira vez em que esse módulo for referenciado em um import).

# my_module.py
class Singleton:

    def __init__(self):
        self.some_attribute = None


singleton = Singleton()

Repare que no código anterior, não foi necessário a criação do método instance(), pois iremos simplificar o uso do pattern através da variável singleton que está declarada no escopo do módulo my_module.py.

>>> from my_module import singleton
>>> singleton.some_attribute = 'some value'

Assim como a implementação, a utilização do pattern também passa a ser simplificada, porém com o mesmo efeito.

>>> from my_module import singleton
>>> singleton.some_attribute
'some value'

Isso é possível graças ao fato de que os módulos do python funcionam como um objeto singleton.

Modo implícito

Uma forma de garantir que haverá somente uma instância de uma determinada classe é a utilização do método especial __new__. O método __new__ é invocado pelo python sempre que uma nova instância de uma determinada classe for criada. O retorno desse método deverá ser a instância da classe em questão. Com isso, podemos transferir o mecanismo do método instance() para o método __new__.

# my_module.py
def Singleton:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

Como a inteligência do método instance() foi transferida para o método __new__ o funcionamento basicamente é o mesmo, porém, a utilização da classe pode gerar confusões, já que não está mais explícito o fato de ser uma classe singleton.

>>> from my_module import Singleton
>>> foo = Singleton()
>>> bar = Singleton()
>>> foo is bar
True
>>> foo.some_attribute = 'some value'
>>> bar.some_attribute
'some value'

Lembre-se, para escrever códigos pythonicos, é sempre bom não ferir o Zen do Python4.

Modo Genérico

Uma forma genérica e reaproveitável de implementar o pattern singleton é combinando o pattern Multiton com o pattern Decorator na criação de um mecanismo que se responsabilize por garantir a criação de uma instância única de qualquer classe.

# my_decorators.py
def singleton(cls):
    instances = {}
    def instance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return instance

Através de uma Closure o decorator de classe implementado no código anterior, define um mapeamento de instâncias tendo como chave as próprias classes.

# my_module.py
from my_decorators import singleton


@singleton
class Singleton:

    def __init__(self):
        self.some_attribute = None

Dessa forma, sempre que uma classe que esteja decorada como no exemplo anterior for instânciada, esse decorator garante a instância única.

>>> from my_module import Singleton
>>> foo = Singleton()
>>> bar = Singleton()
>>> foo is bar
True
>>> foo.some_attribute = 'some value'
>>> bar.some_attribute
'some value'

Porém, dessa forma, também não está explicito o uso do pattern.

Exemplos conhecidos de uso

Python

Como dito anteriormente, o python internamente utiliza o pattern singleton com certa frequência, não somente no mecanismo de modulos, mas também nos objetos None, True e False. Essa é a razão pela qual é considerada uma boa prática, sempre que testar se o valor de um objeto é None, utilizar o operador is ao invés do operador == 5.

Tornado

Um exemplo bem próximo do formato tradicional do pattern singleton é a classe IOLoop do webframework Tornado. Essa classe possui o método instance(), responsável por garantir uma única instancia da classe IOLoop.

Django

O webframework Django também faz uso do pattern singleton, um exemplo é o objeto de configurações django.conf.settings que consiste em uma instância da classe LazySettings. A classe LazySettings do Django não implementa mecanismo de criação de instância para garantir o funcionamento do pattern singleton, pois o objeto settings foi declarado dentro do módulo django.conf e faz uso do mecanismo do python de controle de módulos.

Problemas

Herança

Um dos principais problemas do pattern singleton no python é o fato de não funcionar muito bem com heranças. Objetos de subclasses de uma classe singleton, sempre terão a mesma instância.

# my_module.py
class Singleton:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance


class Foo(Singleton):
    pass


class Bar(Foo):
    pass

Seguindo o código anterior, por mais que aparentem ser classes distintas, o atributo de classe __instance é o mesmo para todas e como o método __new__ não foi reimplementado (o que não faria sentido), a classe Singleton está garantindo a instância única.

>>> from my_module import Foo, Bar
>>> foo = Foo()
>>> bar = Bar()
>>> foo is bar
True

Hoje uma, amanhã várias

Em alguns casos, uma possível mudança de design onde uma classe que antes deveria se comportar como singleton e agora não mais, pode significar um problema, custando horas de refatoração para normalizar o código. Por essa razão, (assim como todos os outros) use esse pattern quando ele for realmente necessário.

Reinventar a roda

O mecanismo de módulo do python já é singleton, então, porque se preocupar em solucionar um problema já resolvido?


  1. "Uma variável global torna um objeto acessível, mas não impede você de instanciar múltiplos objetos". Padrões de Projetos (GoF), pag. 130. 

  2. "Who cares about identity -- it's state (and behavior) we care about!". Singleton? We don't need no stinkin' singleton, Alex Martelli. http://code.activestate.com/recipes/66531/ 

  3. "They are executed only the first time the module name is encountered in an import statement". Modules, Python Documentation. https://docs.python.org/3.4/tutorial/modules.html 

  4. "Explicit is better than implicit.". Zen of Python, Tim Peters, (import this

  5. O operador is é utilizado para verificar se a identidade de dois objetos é a mesma, enquanto que o operador == verifica se o valor de dois objetos é o mesmo. Internamente o operador == funciona como uma sobrecarga do método obj.__eq__(self, other), já no caso do operador is, o efeito seria o mesmo que id(obj1) == id(obj2)