그 동안 클래스와 객체 기본부터 생성자, 캡슐화, 상속, 오버라이딩, MRO, 매직 메서드, 데코레이터, 프로퍼티, 추상 클래스, 제너레이터, 메모리 관리(GC/복사)에 이르기까지 파이썬 객체지향 프로그래밍의 깊이 있는 원리들을 차근차근 정복해 왔습니다. 시리즈의 대미를 장식할 이번 마지막 편에서는 선배 개발자들이 수많은 프로젝트를 거치며 정립한 전형적인 소프트웨어 설계 구조인 '디자인 패턴(Design Pattern)'의 세계를 맛보려 합니다.

프로그램을 만들다 보면 시스템 전체에서 "딱 하나의 객체만 생성되어 공유되어야 하는 클래스"가 반드시 존재합니다. 대표적으로 시스템 설정 정보(Config) 관리자, 로거(Logger), 데이터베이스(DB) 커넥션 풀이 이에 해당합니다. 만약 설정 관리 객체가 호출될 때마다 계속 새로 만들어진다면, 프로그램 각지에서 설정값이 다르게 적용되는 데이터 불일치 문제가 발생하거나 불필요한 메모리가 낭비됩니다.

저 역시 초보 시절, DB 연결을 담당하는 객체를 필요한 함수마다 새로 인스턴스화했다가 소켓 연결이 수백 개로 폭증하여 DB 서버가 터져버린 아찔한 실수를 한 적이 있습니다. 오늘은 시스템 전체에서 오직 단 하나의 객체만 존재하도록 강제하는 싱글톤(Singleton) 패턴의 원리와 파이썬 구현 기법을 정리해 드립니다.


1. 싱글톤(Singleton) 패턴이란 무엇인가?

싱글톤 패턴은 '특정 클래스의 인스턴스가 메모리상에 오직 하나만 존재하도록 보장하고, 어디서든 동일한 인스턴스에 접근할 수 있도록 일관된 통로를 제공하는 디자인 패턴'입니다.

새로운 객체 생성을 요청하더라도(obj = MyClass()), 싱글톤 클래스는 매번 새 객체를 만드는 대신 이미 기존에 생성해 둔 객체의 주소를 그대로 반환합니다.

  • 사용 목적: 메모리 절약, 데이터 일관성 유지, 공유 자원(DB, 파일, 설정)에 대한 동시 접근 제어


2. 파이썬 매직 메서드 __new__로 구현하는 싱글톤

파이썬에서 객체가 생성될 때 가장 먼저 실행되는 것은 __init__(초기화)이 아니라 __new__(생성자)입니다. __new__ 메서드는 객체의 메모리 주소를 실제로 할당하여 리턴하는 역할을 담당하므로, 이 구문을 오버라이딩하면 싱글톤을 가장 완벽하게 구현할 수 있습니다.

Python
class AppConfig:
    # 클래스 변수로 단 하나의 인스턴스 주소를 저장할 공간 확보
    _instance = None

    def __new__(cls, *args, **kwargs):
        # 인스턴스가 아직 생성된 적이 없다면 새로 생성하여 저장
        if cls._instance is None:
            print("[시스템] 최초 1회 객체를 생성합니다.")
            cls._instance = super().__new__(cls)
        # 이미 존재한다면 기존 인스턴스를 그대로 반환
        return cls._instance

    def __init__(self, theme="dark"):
        self.theme = theme

# 테스트: 두 개의 변수에 객체 할당 시도
config1 = AppConfig("dark")
config2 = AppConfig("light")

# 두 변수가 가리키는 메모리 주소 비교
print(f"config1 주소: {id(config1)}")
print(f"config2 주소: {id(config2)}")
print(f"두 객체가 완전히 동일한가? {config1 is config2}")  # 출력: True

config1과 config2는 서로 다른 변수처럼 보이지만, id()로 메모리 주소를 출력해 보면 두 변수가 정확히 동일한 메모리 주소를 바라보고 있음을 확인할 수 있습니다.


3. 더 파이썬다운 싱글톤: 모듈(Module)과 데코레이터 활용

사실 파이썬에는 싱글톤을 만드는 가장 파이썬다운(Pythonic) 강력하고 단순한 방법이 기본으로 내장되어 있습니다. 바로 '파이썬 모듈(Module)'입니다.

파이썬에서 .py 모듈 파일은 프로그램 전체에서 처음 import될 때 딱 한 번만 실행되어 메모리에 적재되며, 이후 다른 파일에서 다시 import하더라도 이미 메모리에 로드된 동일한 모듈 객체를 공유합니다.

1) 모듈을 이용한 최적의 싱글톤 (추천)

Python
# config_module.py 파일 내부
class _Config:
    def __init__(self):
        self.api_key = "SECRET_1234"

# 파일 내에서 직접 객체를 미리 하나만 생성해 둠
config = _Config()

다른 파일에서는 from config_module import config 형태로 미리 생성된 config 객체 하나만 계속 가져다 쓰면 완벽한 싱글톤이 유지됩니다.

2) 클래스 데코레이터를 이용한 싱글톤

재사용성을 높이고 싶다면 앞서 10편에서 배운 데코레이터를 클래스 단위로 적용하여 싱글톤으로 변신시킬 수도 있습니다.

Python
def singleton(cls):
    instances = {}
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get_instance

@singleton
class DatabaseConnection:
    def __init__(self):
        print("DB 커넥션 풀 생성 중...")


4. 싱글톤 패턴 적용 시 주의할 점 (안티 패턴의 위험성)

싱글톤 패턴은 매우 유용하지만, 무분별하게 남용하면 오히려 독이 되어 '안티 패턴(Anti-Pattern)'이 될 수 있습니다.

  1. 모듈 간의 강한 결합(Tight Coupling): 싱글톤 객체가 너무 많은 곳에서 전역적으로 공유되면, A 모듈에서 바꾼 설정값 때문에 멀리 떨어진 B 모듈이 예기치 않게 동작을 멈추는 사이드 이펙트(부작용)가 발생할 수 있습니다.

  2. 단위 테스트(Unit Test)의 어려움: 싱글톤은 상태를 전역으로 유지하므로, 테스트를 수행할 때마다 객체의 상태를 깨끗이 초기화하기가 까다로워 테스트 작성이 어려워집니다.

따라서 진짜 전역으로 공유해야 하는 자원(설정, 로그, DB 연결)에만 제한적으로 적용하는 지혜가 필요합니다.


핵심요약

  • 싱글톤(Singleton) 패턴은 시스템 전체에서 클래스의 인스턴스가 오직 하나만 생성되어 메모리를 공유하도록 보장하는 디자인 패턴입니다.

  • 파이썬에서는 __new__ 매직 메서드 오버라이딩, 클래스 데코레이터, 또는 파이썬 모듈(import)의 기본 동작 특성을 활용해 손쉽게 싱글톤을 구현할 수 있습니다.

  • 설정 관리나 DB 커넥션 등 자원 공유에 유용하지만, 무분별한 사용은 전역 상태 공유로 인한 결합도 증가 및 테스트 난이도 상승을 야기하므로 절제하여 적용해야 합니다.