Python에서 싱글톤 패턴을 간단하고 우아하게 정의하는 방법
- 전역 액세스가 필요한 객체: 설정이나 로깅 시스템과 같은 전역 액세스가 필요한 객체는 싱글톤 패턴으로 구현하는 것이 좋습니다.
- 리소스 절약: 여러 인스턴스를 생성할 필요가 없는 객체는 싱글톤 패턴으로 구현하여 메모리와 다른 리소스를 절약할 수 있습니다.
- 상태 공유: 여러 객체가 동일한 데이터를 공유해야 하는 경우 싱글톤 패턴을 사용하여 공유 객체를 제공할 수 있습니다.
Python에서 싱글톤 패턴을 정의하는 몇 가지 방법이 있습니다. 가장 간단하고 일반적인 방법은 다음과 같습니다.
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
# 싱글톤 객체 사용
singleton = Singleton()
이 코드는 다음과 같은 방식으로 작동합니다.
__new__
메서드는 클래스의 새 인스턴스를 생성할 때 호출됩니다._instance
클래스 변수는 생성된 인스턴스를 저장합니다.__new__
메서드가 호출될 때마다_instance
가None
인지 확인합니다._instance
가None
인 경우 새 인스턴스를 생성하고_instance
에 저장합니다._instance
가None
이 아닌 경우 기존 인스턴스를 반환합니다.
이 방법은 간단하고 효율적이며 대부분의 경우 충분합니다. 하지만 더 복잡한 상황에서는 다음과 같은 다른 방법을 사용할 수도 있습니다.
- 이중 체크 잠금: 이 방법은 여러 스레드에서 싱글톤 인스턴스를 동시에 요청할 경우 발생할 수 있는 경쟁 조건을 방지하는 데 도움이 됩니다.
- 메타클래스 사용: 이 방법은 싱글톤 인스턴스 생성 및 관리를 위한 코드를 더욱 명확하고 간결하게 만들 수 있습니다.
어떤 방법을 사용하든 싱글톤 패턴은 객체의 인스턴스가 하나만 존재하도록 하며 전역 액세스를 제공하는 강력한 도구입니다.
예제 코드: Python에서 싱글톤 패턴 구현
예제 1: 간단한 싱글톤
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
# 싱글톤 객체 사용
singleton = Singleton()
print(singleton) # <__main__.Singleton object at 0x0000017754981040>
singleton2 = Singleton()
print(singleton2) # <__main__.Singleton object at 0x0000017754981040>
설명:
Singleton
클래스는_instance
이라는 클래스 변수를 사용하여 단일 인스턴스를 저장합니다.singleton
및singleton2
변수는 모두 동일한 싱글톤 객체를 참조합니다.
예제 2: 이중 체크 잠금付き 싱글톤
import threading
class Singleton:
_instance_lock = threading.Lock()
_instance = None
def __new__(cls):
with cls._instance_lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
# 싱글톤 객체 사용
singleton = Singleton()
print(singleton) # <__main__.Singleton object at 0x0000017754981040>
singleton2 = Singleton()
print(singleton2) # <__main__.Singleton object at 0x0000017754981040>
- 이 예제는
threading.Lock
을 사용하여 여러 스레드가 동시에 싱글톤 인스턴스를 요청할 경우 발생할 수 있는 경쟁 조건을 방지합니다. __new__
메서드는_instance_lock
을 사용하여 인스턴스 생성을 동기화합니다.
- 두 예제 코드 모두 동일한 기능을 제공하지만, 두 번째 예제는 여러 스레드 환경에서 사용될 경우 더 안전합니다.
- 싱글톤 패턴은 전역 변수와 유사한 장단점을 가지고 있습니다.
- 싱글톤 패턴을 사용하면 코드의 모듈성이 다소 저하될 수 있습니다.
- 싱글톤 패턴을 사용해야 할지 확실하지 않은 경우 다른 방법을 사용하는 것이 좋습니다.
Python에서 싱글톤 패턴 대체 방법
- 테스트 어려움: 싱글톤 인스턴스는 전역 변수와 유사하기 때문에 단위 테스트를 어렵게 만들 수 있습니다.
- 코드 모듈성 저하: 싱글톤 패턴은 코드의 모듈성을 다소 저하시킬 수 있습니다.
- 명시적 의존성 도입: 싱글톤 패턴은 다른 객체 간의 명시적 의존성을 도입할 수 있습니다.
따라서 싱글톤 패턴을 사용해야 할지 확실하지 않은 경우 다음과 같은 대체 방법을 고려할 수 있습니다.
의존성 주입:
의존성 주입은 객체가 필요한 의존성을 직접 생성하는 대신 외부로부터 제공받는 디자인 패턴입니다. 의존성 주입을 사용하면 싱글톤 패턴 없이도 객체 간의 느슨한 결합을 유지할 수 있습니다.
from typing import Dict
class Database:
def __init__(self, connection_string: str):
self.connection_string = connection_string
def connect(self):
# 데이터베이스에 연결합니다.
pass
def query(self, query: str) -> Dict[str, str]:
# 데이터베이스 쿼리 실행 및 결과 반환
pass
def get_database() -> Database:
# 실제 데이터베이스 객체를 반환합니다.
return Database("connection_string")
def main():
database = get_database()
database.connect()
results = database.query("SELECT * FROM users")
for row in results:
print(row)
if __name__ == "__main__":
main()
클래스 변수:
클래스 변수는 클래스의 모든 인스턴스에서 공유되는 변수입니다. 싱글톤 패턴과 유사하게 클래스 변수를 사용하여 전역 액세스를 제공할 수 있습니다. 하지만 클래스 변수는 여러 인스턴스에서 변경될 수 있으므로 주의해서 사용해야 합니다.
class Settings:
debug = False
def get_settings():
return Settings
def main():
settings = get_settings()
if settings.debug:
print("디버그 모드")
else:
print("정상 모드")
if __name__ == "__main__":
main()
프로젝트 설정:
프로젝트 설정 파일을 사용하여 전역 설정을 저장할 수 있습니다. 이 방법은 싱글톤 패턴이나 클래스 변수보다 더 유연하지만 설정 파일을 읽고 쓰는 코드를 추가해야 합니다.
import configparser
config = configparser.ConfigParser()
config.read_file("settings.ini")
debug = config["DEFAULT"]["debug"]
if debug:
print("디버그 모드")
else:
print("정상 모드")
기타 방법:
위에서 언급한 방법 외에도 다음과 같은 방법을 사용할 수 있습니다.
- 프로세스 간 통신: 여러 프로세스에서 동일한 인스턴스를 공유해야 하는 경우 프로세스 간 통신 메커니즘을 사용할 수 있습니다.
- 레지스트리: Windows에서는 레지스트리를 사용하여 전역 설정을 저장할 수 있습니다.
어떤 방법을 사용하든 싱글톤 패턴의 단점을 인지하고 상황에 맞는 적절한 방법을 선택하는 것이 중요합니다.
python design-patterns singleton