싱글턴(Singleton) 패턴 : 기초

파이썬에서 싱글턴 패턴의 다양한 구현 방법

written by tiaz0128

싱글턴(Singleton)

생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나이고 최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴한다. ~ 위키백과 ~

싱글턴(Singleton) 패턴은 특정 클래스의 인스턴스가 프로그램 전체에서 단 하나만 생성되도록 보장하는 패턴입니다.

그냥 전역 변수로 쓰면 안돼?

전역 변수를 쓰지 않고 싱글턴 패턴을 쓰는 가장 큰 이유는 접근 제어 때문입니다.

언제 써야 하나

굳이 여러번 만들어질 필요가 없는 객체를 생각하면 됩니다. 대표적인 경우는 아래와 같습니다.

파이썬에서 싱글턴 구현하기

파이썬에서 여러가지 방법으로 싱글턴을 구현 할 수 있습니다. 여기에서는 5가지 방법으로 싱글턴을 구현해보고 각각의 특성을 알아보겠습니다.

  1. GoF(Gang of Four)
  2. __new__
  3. metaclass
  4. Eager
  5. Thread Safe

싱글턴 : GoF(Gang of Four)

클래스 다이어그램

%%{
  init: {
    'theme': 'base',
    'themeVariables': {
      'primaryColor': '#2a3844',
      'lineColor': '#fff',
      'primaryTextColor': '#fff',
      'tertiaryColor': '#fff'
    }
  }
}%%

classDiagram
    direction BT

    class Singleton{
        -_instance: Singleton

        +get_instance() Singleton
    }

구현

가장 클래식한 방법으로 GoF에서 말하는 패턴입니다.

  1. __init__ 메서드를 Override해서 사용하지 못하게 한다.
  2. 클래스 메서드 get_instance를 호출하여 인스턴스를 생성 또는 이미 만들어져 있는 인스턴스를 리턴 한다.
gof/singleton.py
from typing import Self

class Singleton:
    _instance = None

    def __init__(self) -> None:
        raise RuntimeError("Call instance() method")

    @classmethod
    def get_instance(cls) -> Self:
        if not cls._instance: 
            cls._instance = super().__init__(cls)
        return cls._instance
gof/test_gof_singleton.py
from gof.singleton import Singleton

def test_gof_singleton():
    obj_1 = Singleton.get_instance()
    obj_2 = Singleton.get_instance()

    assert obj_1 is obj_2

싱글턴 : __new__

클래스 다이어그램

%%{
  init: {
    'theme': 'base',
    'themeVariables': {
      'primaryColor': '#2a3844',
      'lineColor': '#fff',
      'primaryTextColor': '#fff',
      'tertiaryColor': '#fff'
    }
  }
}%%

classDiagram
    direction BT

    class Singleton{
        -_instance: Singleton

        +__new__() Singleton
    }

구현

파이썬에서 가장 일반적으로 구현할 수 있는 방법입니다.

  1. __new__ 메서드를 오버라이드(Override) 한다.
  2. __new__ 메서드에서 인스턴스를 생성 또는 이미 만들어져 있는 인스턴스를 리턴 한다.
new/singleton.py
from typing import Self

class Singleton:
    _instance = None

    def __new__(cls) -> Self:
        if not cls._instance:
            cls._instance = super().__new__(cls)

        return cls._instance
new/test_new_singleton.py
from new.singleton import Singleton

def test_new_singleton():
    obj_1 = Singleton()
    obj_2 = Singleton()

    assert obj_1 is obj_2

싱글턴 : 메타클래스(metaclass)

메타클래스(metaclass)는 클래스의 클래스입니다. 즉, 메타클래스는 클래스를 생성하는 것입니다. 파이썬에서 모든 것은 객체이며, 클래스도 객체입니다. 따라서 클래스를 생성하는 것 역시 객체인데, 이를 메타클래스라고 합니다.

사용자 정의 메타클래스를 만드는 방법은 크게 두 가지가 있습니다.

  1. type을 상속받아서 메타클래스를 만들고 메서드를 오버라이드
  2. type을 사용하여 동적으로 클래스를 생성하는 방식

클래스 다이어그램

%%{
  init: {
    'theme': 'base',
    'themeVariables': {
      'primaryColor': '#2a3844',
      'lineColor': '#fff',
      'primaryTextColor': '#fff',
      'tertiaryColor': '#fff'
    }
  }
}%%

classDiagram
    direction BT

    class SingletonMeta{
        - _instances: dict

        - __call__() Singleton
    }

    class Singleton{
        + some_business_logic()
    }

    Singleton --|> SingletonMeta

구현

type을 상속하여 메타클래스를 만드는 방법을 이용하여 싱글턴 패턴을 만들 수 있습니다.

  1. type 을 상속하는 metaclass를 정의한다.
  2. __call__ 메서드를 오버라이드 한다.
  3. 만들고 싶은 싱글턴 클래스에 metaclass를 지정 합니다.
meta/meta.py
import logging

class SingletonMeta(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        logging.info("metaclass __call__")

        if cls not in cls._instances:
            instance = super().__call__(*args, **kwargs)
            cls._instances[cls] = instance
        
        return cls._instances.get(cls)
meta/singleton.py
from meta.meta import SingletonMeta

class Singleton(metaclass=SingletonMeta):
    def some_business_logic(self):
        ...
meta/test_meta_singleton.py
from meta.singleton import Singleton

def test_meta_singleton():
    obj_1 = Singleton()
    obj_2 = Singleton()

    assert obj_1 is obj_2

metaclass=를 사용해서 해당 클래스의 생성 과정에 개입하는 클래스를 지정 한다고 생각하면 됩니다. 여기서는 __call__ 메서드를 오버라이드 했기 때문에 객체가 생성되는 시점에 SingletonMeta 메타클래스의 __call__ 메서드가 호출 되는 것입니다.

INFO     root:singleton_meta.py:8 metaclass __call__
INFO     root:singleton_meta.py:8 metaclass __call__

앞서 본 싱글턴 패턴은 클래스 자체에 싱글턴을 구현한 반면, 메타클래스는 상속하는 형태로 코드를 작성 할 수 있습니다. 보다 객체지향스럽게 코드를 작성하는데 도움이 됩니다.

마무리

지금까지 싱글턴 패턴의 기본 개념과 파이썬에서 구현할 수 있는 세 가지 방법에 대해 알아보았습니다. 😊

각각의 구현 방식은 저마다의 특징이 있습니다. GoF 방식은 가장 전통적이지만 직관적이며, __new__ 방식은 파이썬스러운 구현이 가능합니다. 메타클래스를 활용한 방식은 상속을 통해 재사용성이 높은 코드를 작성할 수 있다는 장점이 있습니다.

다음으로

다음글에서 계속 됩니다.

싱글턴(Singleton) 패턴 : 심화에서는 남은 두 가지 구현 방식인 Eager Initialization과 Thread Safe한 싱글턴 구현에 대해 자세히 다루도록 하겠습니다. 특히 멀티스레드 환경에서 안전하게 싱글턴을 구현하는 방법에 대해 깊이 있게 살펴보겠습니다.

python 디자인 패턴 싱글턴

tiaz0128

Eat Sleep Coding.

Never Never GiveUp.

Security  |  BackEnd  |  Multi Cloud