카테고리 없음

파이썬 싱글톤 패턴 (Singleton pattern in python)

seul chan 2020. 12. 30. 23:26

singleton pattern in python

싱글턴 패턴: 인스턴스를 하나만 생성하여 불필요한 메모리 낭비를 방지하고 해당 클래스의 인스턴스를 바로 호출 할 수 있게 하는 패턴.

한번에 아주 많은 요청을 처리해야하는데 매번 클라이언트를 호출해야 할 일이 있어 처음에는 전역변수를 사용하였다가 싱글톤 패턴을 사용하는 김에 정리해둔다.

클래스로 직접 구현하거나 데코레이터로 구현이 가능하다. 각 방법과 장단점을 소개

# 예시 BaseClient - 공통으로 사용
class BaseClient:
    @classmethod
    def class_call(cls):
        return {'result': True}
    def call(self):
        return {'result': True}

1. decorator

def singleton_decorator(class_):
    instances = {}
    def getinstance(*args, **kwargs):
        if class_ not in instances:
            instances[class_] = class_(*args, **kwargs)
        return instances[class_]
    return getinstance

@singleton_decorator
class DecoratorClient(BaseClient):
    pass

클래스로 만들어서 상속받는 아래의 방법들보다 직관적인 방법이다.

다만 object가 아닌 클래스 자체는 singleton 객체가 아닌 함수이기 때문에 클래스 메소드를 부를 수 없다.

>>> c1 = DecoratorClient()
>>> c2 = DecoratorClient()
>>> c1 == c2
True
>>> DecoratorClient.class_call()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'function' object has no attribute 'class_call'

2. base class 생성

싱글톤 클래스를 생성하여 상속받는 방법. 순수 클래스를 사용하는 장점이 있지만 상속을 받아야하는 단점이 있고, 상속중에 __new__ 클래스가 override 되지 않게 주의해야한다.

class SingletonClass(object):
    _instance = None
    def __new__(class_, *args, **kwargs):
        if not isinstance(class_._instance, class_):
            class_._instance = object.__new__(class_, *args, **kwargs)
        return class_._instance

class ClassClient(SingletonClass, BaseClient):
    pass

실제로 호출해보면 클래스의 메소드도 모두 동일한 것을 볼 수 있다.

>>> c1 = ClassClient()
>>> c2 = ClassClient()
>>> c1 == c2
True
>>> c1.call() == c2.call()
True
>>> c1.class_call() == ClassClient.class_call()
True

3. metaclass 사용

type으로 싱글톤 클래스를 정의하고 메타클래스를 사용하여 구현이 가능하다.

개인적으로는 가장 좋은 방법이라고 생각.

class SingletonType(type):
    def __call__(cls, *args, **kwargs):
        try:
            return cls.__instance
        except AttributeError:
            cls.__instance = super().__call__(*args, **kwargs)
            return cls.__instance

class MetaclassClient(BaseClient, metaclass=SingletonType):
    pass

__call__ 메소드는 try/except을 사용하지 않고 instances 변수를 사용해서 구현해도 된다.

>>> c1 = MetaclassClient()
>>> c2 = MetaclassClient()
>>> c1 == c2
True
>>> c1.call() == c2.call()
True
>>> MetaclassClient.class_call() == c1.class_call()
True