Hun's Blog

[Python] 파이썬으로 Singleton 패턴 구현하기 본문

Language/Python

[Python] 파이썬으로 Singleton 패턴 구현하기

jhk-im 2020. 4. 6. 20:53

디자인 패턴에 관련된 자세한 내용은 아래 링크에 정리해 두었다.

https://jroomstudio.tistory.com/20?category=386216

 

[Android] 디자인패턴 1 - 디자인패턴이란?

디자인 패턴 mvc - mvp - mvvm을 사용해보기위해 공부해보니 안드로이드 개발 시 사용하는 디자인패턴이라는 것을 알게되었다. 그렇다면 먼저 디자인 패턴에 대해 공부를 해봐야 할 것 같아서 찾아보기 시작했는데..

jroomstudio.tistory.com

Singleton
하나의 클래스에 대해 어플리케이션이 시작될 때 최초 한번만 메모리를 할당하고 그 메모리에 인스턴스를 생성한다. 즉, 인스턴스를 단 하나만 생성한다.

장점
- 고정된 메모리영역을 얻어 하나의 인스턴스만 생성하기 때문에 메모리 낭비를 방지한다.
- 인스턴스가 전역적으로 사용될 수 있기에 다른 클래스의 인스턴스들이 데이터를 공유하고 변경할 수 있다.

단점
- 싱글톤 인스턴스에게 많은일을 위임하거나 데이터를 공유시킬 경우 다른 클래스의 인스턴스간에 결합도가 높아져 개방폐쇄원칙에 위배된다.
- 멀티스레드 환경에서 데이터 동기화 문제가 발생할 수 있음 (Synchronized 키워드 활용)
- 너무 많이 사용하지 않도록 한다.

 

 *싱글톤은 하나의 클래스에 단 하나의 인스턴스를 허용하는 패턴이다. 

 

1. __new__ 생성자를 이용하여 Sinlgeton 구현

 

Singleton.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Singleton(object):
    def __new__(cls):
        if not hasattr(cls,'instance'):
            print('create')
            cls.instance = super(Singleton, cls).__new__(cls)
        else:
            print('recycle')
        return cls.instance
 
print('1번째 생성')
s1 = Singleton() # create
print('2번째 생성')
s2 = Singleton() # recycle
print('s1 == s2')
print(s1==s2) # true
 
 

1) class Singleton(object):

-> 자바와 마찬가지로 클래스가 존재한다. 

   -> 또한 동일하게 객체로 만들어 사용하여 객체지향 프로그래밍을 구현할 수 있다. 

-> 클래스명 옆에 (object)는 python3 에서 자동으로 상속받기 때문에 없어도 오류가 발생하지 않는다.

 

2) def __new__(cls): 

* __new__

-> 인스턴스를 *새로 만들 때 처음으로 실행되는 메소드이다. 

-> 인스턴스가 생성될때 어떻게 할 것인지 재정의 하여 sinlgeton을 구현할수있다. 

-> 입력값인 (cls) 는 변경할 수 없다. 

-> cls 는 해당 클래스가 생성되면서 새로 생긴 class 즉 객체이다. 

 

3) if not hasattr(cls,'instance'):

-> if not 은 fase 일때 실행하겠다는 의미이다. 

-> hasattr(object,name) 메소드는 object에 nam에 해당하는 attribute 가 있으면 true 없으면 false 를 반환한다. 

-> print('create') 으로 else 는 print('recycle') 로 각각 생성과 재사용을 구분한다. 

 

4) cls.instance = super(Singleton, cls).__new__(cls)

-> cls의 instance 변수에 새로운 인스턴스를 생성하여 담는다. 

-> 없을경우 새로운 객체를 생성하여 instance에 담아 반환한다. 

-> 클래스 내부에 instance가 존재하면 생성된 instance를 반환한다.

 

5) return cls.instance

-> return 하지않으면 객체를 반환하지 않으므로 반드시 return 해주어야 한다. 

-> hasattr() 메소드로 true/false 를 판별하기 때문에 생성 후에는 기존의 인스턴스만 반환하게된다. 

동작결과

 

2. Lazy instantiation 

Lazy instantiation (게으른 초기화) 

- 객체를 필요할 때 만든다. 

- 사용할 수 있는 리소스가 제한적인 상황에서 꼭 필요한 시점에 객체를 생성한다. 

 

LazyInstantiation.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class LazyInstantiation:
    _instance = None
    def __init__(self):
        if not LazyInstantiation._instance:
            print('__init__ method called but nothing is created')
        else:
            print('instance already created:', self.getInstance())
 
    @classmethod
    def getInstance(cls):
        if not cls._instance:
            cls._instance = LazyInstantiation()
        return cls._instance
 
# __init__ method called but nothing is created
= LazyInstantiation()
print(s._instance) # None
# __init__ method called but nothing is created
s1 = LazyInstantiation.getInstance()
s2 = LazyInstantiation.getInstance()
print('s와 s1은 서로 같은 인스턴스인가? -> ',s==s1) # True
print('s1 : ',s1._instance) # <__main__.LazyInstantiation object at 0x03A6B340>
print('s2 : ',s2._instance) # <__main__.LazyInstantiation object at 0x03A6B340>
 
s3 = LazyInstantiation() # instance already created: <__main__.LazyInstantiation object at 0x03A6B340>
print('s3 : ',s3._instance) #  <__main__.LazyInstantiation object at 0x03A6B340>
 

1) class LazyInstantiation: 

-> 클래스 생성

 

2) _instance = None 

-> _instance 라는 전역변수를 생성하고 값을 None(null) 으로 한다. 

 

3) def __init__(self):

* __init__

-> 자바의 생성자와 비슷한 역할을 한다. 

-> 객체의 인스턴스를 생성할 때 자동으로 호출된다. 

   -> __new__ 메소드는 객체를 생성할 때 호출되는 것이고 __init__ 은 객체가 활성화 될 때 호출된다. 

       -> 그렇기 때문에 15번 라인과 같이 생성하기 전에 메세지를 출력한 것이다. 

-> 첫번째 인자값은 반드시 self 여야 한다. 

 

4) if not LayzInstantiation._instance: 

-> 전역변수 _instance 가 None 이면 fasle 이기 때문에 None 일때 실행된다. 

-> 값이 들어있을 때는 instance를 출력한다. 

 

5) @classmethod

-> 정적 메소드 구현방법 중 하나 

-> 인스턴스를 생성하지 않아도 메서드를 바로 실행할 수 있다. 

    -> 스크립트의 19,20번 라인을 보면 클래스를 클래스() 형태로 생성하지 않고 getInstance()로 접근한다. 

    -> 클래스가 생성되지 않았음에도 getInstance() 를 통해 클래스의 instance를 가져온다. 

 

6) def getInstance(cls)

-> cls는 클래스를 의미한다. 여기서 클래스는 메소드가 속한 클래스를 의미한다. 

-> if not cls._instance

   -> 해당 클래스의 전역변수 _instance 가 None 이라면 LazyInstantiation() 을 통해 인스턴스를 생성하고 담는다.

   -> 생성된 혹은 생성되어있는 cls_instance 를 반환한다. 

 

출력결과 

결과

-> __init__ method called but nothing is created 메세지 출력

   -> 이미 해당 클래스에서 run이 된 순간부터 __init__ 클래스가 호출된다. 

   -> 아직 전역변수 _instance에 값이 없으므로 아직 만들어지지 않았다는 메세지가 출력되었다. 

 

-> None , __init__ method called but nothing is created 메세지 출력

   -> s = LazyInstantiation() 

   -> 1번 예제와 같이 클래스 객체를 생성해보았다. 

   -> 생성 후 s._instance 를 출력했을때 생성이 되지 않아 None 으로 출력되었다. 

   -> 클래스가 다시 활성화 되었기 때문에 __init__ 이 실행되었고 여전히 _instance 는 None 이기 때문에 if not 이 실행된다.

 

-> s,s1 을 getInstance() 메소드로 인스턴스를 가져온다.

   -> s와 s1은 서로 같은 인스턴스인가? ->  True

   -> 맨 처음 s1 = LayzInstantiation.getInstance() 를 호출하였다. 

      -> 클래스 전역변수인 _instance가 None 이므로 LazyInstantiation() 으로 인스턴스를 생성한다. 

   -> 두번째로 s2 = LayzInstantiation.getInstance() 를 호출하였다. 

      -> 이전에 인스턴스가 생성되어 _instance에 값이 들어갔으므로 새로 생성하지 않고 그대로 반환한다. 

   -> s1._instance 와 s2._instance를 출력해보는 같은 값을 가지고 있다

   -> 그러므로 s1과 s2 는 서로 같기 때문에 True 를 반환한다. 

 

-> instance already created: <__main__.LazyInstantiation object at 0x03A6B340> 출력 

   -> s3 = LazyInstantiation() 으로 다시한번생성 

   -> 클래스가 활성화되면서 __init__ 이 호출되고 이번엔 _instance에 값이 있으므로 else를 실행한다. 

   -> s3._instance 를 출력해보면 이전에 생성된 instance와 동일한 것을 확인할 수 있다. 

 

 

// __new__ 로 생성한 것과 간단하게 차이를 나눠보자면 

__new__ -> 클래스 객체를 생성할 때 동작하고 이곳에서 인스턴스 생성작업을 한다.  

           -> 클래스를 생성하는 첫번째 순간에 인스턴스가 반드시 생성되어 반환된다. 

__init__ -> 클래스가 활성화 될때 실행되며 단순히 인스턴스의 생성 여부를 반환한다. 

          -> 내부 클래스인 getInstance 를 통해서 인스턴스를 생성하고 반환한다. 

 

 

3. Monostate Singleton

- 싱글톤 패턴에 의하면 클래스의 객체는 하나만 존재해야 한다. 

- 일부 개발자는 객체 생성 여부보다 객체의 상태와 행위가 더 중요하다고 한다. 

- 모노스테이트는 생성된 여러 객체가 같은 상태를 공유하는 패턴이다. 

 

MonostateSingleton.py 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MonostateSingleton:
    __shared_state = {'a':'b'}
    def __init__(self):
        self.__dict__ = self.__shared_state
 
m1 = MonostateSingleton()
m2 = MonostateSingleton()
print(m1) # <__main__.MonostateSingleton object at 0x02C4B0E8>
print(m2) # <__main__.MonostateSingleton object at 0x02C4B118>
 
m1.a = 1
m2.b = 2
print(m1.__dict__) # {'a': 1, 'b': 2}
print(m2.__dict__) # {'a': 1, 'b': 2}x03A6B340>
 

1) class MonostateSingleton: 

-> 클래스 생성

 

2) __shared_state = {'a':'b'}

- {'a':'b'} 딕셔너리가 생성되었다. 

- 리스트와 비슷하지만 각각의 요소가 key와 value로 구성되어있다. 

 

3) def __init__(self):  -> self.__dict__ = self.__shared_state 

 *__dict__ 

-> 현재 인스턴스와 클래스의 속성을 딕셔너리로 가지고있다. 

-> __dict__ 에 전역변수로 선언한 {'a':'b'} 딕셔너리를 셋팅한다. 

 

 

출력결과

-> m1 = MonostateSingleton() , m2 = MonostateSingleton() 

  -> m1 과 m2 를 출력해보니 서로 다른 인스턴스임을 확인할 수 있다. 

 

-> 객체가 생성될 때 __dict__ 에 전역변수 {'a':'b'} 가 생성된다. 

-> __dict__에 셋팅된 딕셔너리는 다음과 같이 호출하여 값을 셋팅할 수 있다. 

   -> m1.a =1 , m2.b =2

   -> 서로 다른 인스턴스인 m1과 m2에 각각 다른 값을 입력한다. 

 

-> m1.__dict__ , m2.__dict 를 출력했을 때 같은 값을 출력한다. 

 

// 인스턴스가 다르더라도 __dict__ 를 활용하면 같은 상태를 공유할 수 있다. 

 

 

4. Singleton + metaclass

- 메타 클래스는 클래스의 클래스이다. 

- 클래스는 메타 클래스의 인스턴스가 된다. 

- 메타클래스를 활용하여 이미 정의된 클래스를 통해 새로운 클래스를 생성한다. 

 

MetaclassSingleton.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class MetaclassSingleton(type):
    _instance = {}
    def __call__(cls,*args,**kwargs):
        if cls not in cls._instance:
            cls._instance[cls] = super(MetaclassSingleton, cls).__call__(*args,*kwargs)
        return cls._instance[cls]
 
class TestMetaclass(metaclass=MetaclassSingleton):
    pass
 
class SecondMetaclass(metaclass=MetaclassSingleton):
    pass
 
= TestMetaclass()
t1 = TestMetaclass()
print(t) # <__main__.TestMetaclass object at 0x037FB100>
print(t1) # <__main__.TestMetaclass object at 0x037FB100>
 
t2 = SecondMetaclass()
t3 = SecondMetaclass()
print(t2) # <__main__.SecondMetaclass object at 0x037FB310>
print(t3) # <__main__.SecondMetaclass object at 0x037FB310>
print(MetaclassSingleton._instance)
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
 

1) class MetaclassSingleton(type)

-> type 을 상속받은 클래스이다. 커스텀 메타클래스를 만들기 위해 상속받는다. 

 

2) _instance = {}

-> 전역변수인 _instance를 비어있는 딕셔너리로 생성한다. 

 

3) def __call__(cls,*args,*kwargs)

*__call__

-> 클래스의 객체를 호출할수 있게 도와주는 메소드이다. 

-> 객체를 메소드 호출 방식으로 가져올수있게 하는 매직 메소드이다. 

*args

-> arguments의 줄임말 

-> 다른 단어를 써도 된다. 

-> 이 지시어는 복수의 인자를 함수로 받고자 할 때 사용된다. 

*kwargs

-> keyword argument 줄임말 

-> 키워드를 제공한다.  

-> 딕셔너리 형태로 {'키워드':'특정값'} 함수 내부로 전달된다. 

 

4) if cls not in cls._instance: 

-> 현재 cls가 _instance 딕셔너리에 없다면 

  -> cls._instance[cls] = super(MetaclassSingleton, cls).__call__(*args,*kwargs)

     -> MetaclassSingleton 인스턴스를 현재 클래스로 생성하여 _instance 딕셔너리에 추가한다. 

 

5) TestMetaclass() , SecondMetaclass()

-> metaclass=MetaclassSingleton 으로 지정 한다. 

 

출력결과 1

-> t = TestMetaclass() ,t1 = TestMetaclass()

   -> t를 TestMetaclass() 로 생성하였다. 

   -> metaclass 로 지정한 클래스가 생성되면 __call__ 메소드가 호출된다. 

   -> _instance 딕셔너리를 검사하여 TestMetaclass로 생성한 인스턴스가 있는지 확인한다. 

   -> 없으므로 새로 생성한다. 

   -> t1 도 동일하게 생성한다. 

   -> 이번엔 이미 인스턴스가 존재함으로 _instance 딕셔너리에서 가져온 인스턴스를 반환한다. 

   -> 출력해보면 t,t1 은 서로 같은 인스턴스임을 확인할 수 있다. 

 

-> t2 = SecondMetaclass(), t3 = SecondMetaclass()

-> t 와 동일한 방법으로 t2를 생성하되 이번엔 SecondMetaclass() 로 생성하였다. 

-> _instance 에는 SecondMetaclass() 로 생성한 인스턴스가 없기때문에 새로 생성하여 추가한다.

-> t3 는 이미 인스턴스가 존재함으로 _instance 딕셔너리에서 가져온 인스턴스를 반환한다. 

-> 출력해보면 t2,t3 는 서로 같은 인스턴스임을 확인할 수 있다. 

 

출력결과2 

-> MetaclassSingleton._instance 를 출력해보면 TestMetaclass() 로 생성한 인스턴스와 SecondMetaclass() 로 생성한 인스턴스가 각각 딕셔너리 형태로 추가된 것을 확인할 수 있다. 

 

 

참고

https://medium.com/@chs99051868/python-design-pattern-singleton-963f4a796d7f

 

Python Design Pattern — Singleton

이 포스팅은 파이썬 디자인 패턴 2/e 을 참고로 쓴 글입니다.

medium.com