싱글턴(Singleton) 패턴 : 기초
파이썬에서 싱글턴 패턴의 다양한 구현 방법

싱글턴(Singleton)
생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나이고 최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴한다. ~ 위키백과 ~
싱글턴(Singleton) 패턴은 특정 클래스의 인스턴스가 프로그램 전체에서 단 하나만 생성되도록 보장하는 패턴입니다.
그냥 전역 변수로 쓰면 안돼?
전역 변수를 쓰지 않고 싱글턴 패턴을 쓰는 가장 큰 이유는 접근 제어 때문입니다.
- 전역 변수: 전역 변수는 애플리케이션의 어느 곳에서나 직접 접근하고 수정할 수 있습니다. 이는 데이터의 무결성을 보장하기 어렵게 만들고, 예기치 않은 변경으로 인한 버그의 원인이 될 수 있습니다.
- 싱글턴 패턴: 싱글턴 패턴을 사용하면, 인스턴스의 생성과 접근을 제어하는 메서드를 통해 객체에 접근합니다. 이는 객체의 상태와 생명주기를 더 잘 제어할 수 있게 해주며, 데이터의 무결성을 유지하는 데 도움이 됩니다.
언제 써야 하나
굳이 여러번 만들어질 필요가 없는 객체를 생각하면 됩니다. 대표적인 경우는 아래와 같습니다.
- 데이터베이스 연결
- 로깅
- 설정 정보
- 캐시
파이썬에서 싱글턴 구현하기
파이썬에서 여러가지 방법으로 싱글턴을 구현 할 수 있습니다. 여기에서는 5가지 방법으로 싱글턴을 구현해보고 각각의 특성을 알아보겠습니다.
- GoF(Gang of Four)
- __new__
- metaclass
- Eager
- 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에서 말하는 패턴입니다.
__init__
메서드를 Override해서 사용하지 못하게 한다.- 클래스 메서드
get_instance
를 호출하여 인스턴스를 생성 또는 이미 만들어져 있는 인스턴스를 리턴 한다.
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
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 }
구현
파이썬에서 가장 일반적으로 구현할 수 있는 방법입니다.
__new__
메서드를 오버라이드(Override) 한다.__new__
메서드에서 인스턴스를 생성 또는 이미 만들어져 있는 인스턴스를 리턴 한다.
from typing import Self
class Singleton:
_instance = None
def __new__(cls) -> Self:
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
from new.singleton import Singleton
def test_new_singleton():
obj_1 = Singleton()
obj_2 = Singleton()
assert obj_1 is obj_2
싱글턴 : 메타클래스(metaclass)
메타클래스(metaclass)는 클래스의 클래스입니다. 즉, 메타클래스는 클래스를 생성하는 것입니다. 파이썬에서 모든 것은 객체이며, 클래스도 객체입니다. 따라서 클래스를 생성하는 것 역시 객체인데, 이를 메타클래스라고 합니다.
사용자 정의 메타클래스를 만드는 방법은 크게 두 가지가 있습니다.
type
을 상속받아서 메타클래스를 만들고 메서드를 오버라이드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
을 상속하여 메타클래스를 만드는 방법을 이용하여 싱글턴 패턴을 만들 수 있습니다.
- type 을 상속하는
metaclass
를 정의한다. __call__
메서드를 오버라이드 한다.- 만들고 싶은 싱글턴 클래스에
metaclass
를 지정 합니다.
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)
from meta.meta import SingletonMeta
class Singleton(metaclass=SingletonMeta):
def some_business_logic(self):
...
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 스타일의 전통적인 구현 방식
- Python의 __new__ 메서드를 활용한 방식
- 메타클래스를 활용한 객체지향적 구현 방식
각각의 구현 방식은 저마다의 특징이 있습니다. GoF 방식은 가장 전통적이지만 직관적이며, __new__ 방식은 파이썬스러운 구현이 가능합니다. 메타클래스를 활용한 방식은 상속을 통해 재사용성이 높은 코드를 작성할 수 있다는 장점이 있습니다.
다음으로
싱글턴(Singleton) 패턴 : 심화에서는 남은 두 가지 구현 방식인 Eager Initialization과 Thread Safe한 싱글턴 구현에 대해 자세히 다루도록 하겠습니다. 특히 멀티스레드 환경에서 안전하게 싱글턴을 구현하는 방법에 대해 깊이 있게 살펴보겠습니다.