본문 바로가기
Practical Python Design Patterns

The Singleton Pattern

by 자동매매 2023. 3. 28.

The Problem

 

We only have a single file to update.

Let s create a new file called logger.py and write the following code in the file:

 

logger.py

def log_message(msg):
    with open("./var/log/filename.log", "a") as log_file:
        log_file.write("{0}\n".format(msg))


if __name__ == "__main__":
    log_message("error!")

Now, we can use our new logger function to write log messages to the file system in our main_script.py file:

 

main_script.py

import logger

for i in range(4):
    logger.log_message("log message {}".format(i))

 

 

메시지를 유발하는 이벤트의 심각도를 설명하는 데 사용되는 용어를 사용한 메시지 구분

  • Critical
  • Error
  • Warning
  • Info
  • Debug

 

After upgrading our logging function, it looks like this:

 

logger1.py

def critical(msg):
    with open("./var/log/filename.log", "a") as log_file:
        log_file.write("[CRITICAL] {0}\n".format(msg))

def error(msg):
    with open("./var/log/filename.log", "a") as log_file:
        log_file.write("[ERROR] {0}\n".format(msg))

def warn(msg):
    with open("./var/log/filename.log", "a") as log_file:
        log_file.write("[WARN] {0}\n".format(msg))

def info(msg):
    with open("./var/log/filename.log", "a") as log_file:
        log_file.write("[INFO] {0}\n".format(msg))

def debug(msg):
    with open("./var/log/filename.log", "a") as log_file:
        log_file.write("[DEBUG] {0}\n".format(msg))

 

You do not need to change the way you import the code; you just use the level of message you want to save, as follows:

 

test_error_log.py

import logger1

try:
    a = 1 / 0
except:
    logger1.error("something went wrong")

 

If you look at the output of the _test_error_log.py__ function, you will see that the message now has the level added to the line as a prefix:

 

[ERROR] something went wrong

 

반복되는 부분은 코드를 복제하는 모든 메소드에서 추출하고자 하는 부분입니다. 다음 단계는 모든 함수가 원래 기능을 잃지 않고 함수를 사용할 수 있도록 충분히 함수를 일반화하는 것입니다.
반복되는 각 기능에서 접두사는 기능마다 다른 유일한 것입니다. 따라서 메시지와 수준을 매개변수로 사용하는 함수를 작성한다면 이 함수를 다른 함수 각각에서 사용할 수 있고 각 경우에 한 줄의 코드로 줄일 수 있습니다.
이제 다른 프로젝트에서 사용할 수 있는 더 짧고 명확한 로거가 있습니다.

 

logger2.py

def write_log(level, msg):
    with open("./var/log/filename.log", "a") as log_file:
        log_file.write("[{0}] {1}\n".format(level, msg))
def critical(msg):
    write_log("CRITICAL", msg)
def error(msg):
    write_log("ERROR", msg)
def warn(msg):
    write_log("WARN", msg)
def info(msg):
    write_log("INFO", msg)
def debug(msg):
    write_log("DEBUG", msg)


if __name__ == "__main__":
    write_log('INFO', '메시지를 입력하세요!')
    error('에러가 발생하였습니다')

 

로거가 수행하기를 원하는 마지막 작업은 다른 프로젝트의 메시지를 동일한 파일에 쓰는 것입니다.
이 문제를 피하기 위해 메시지를 기록할 때 호출하는 함수에 파일 이름을 매개변수로 추가하기만 하면 됩니다.
계속해서 이 변경 사항을 구현하십시오. write_log 함수는 여전히 파일 이름 매개변수를 사용하므로 다음 코드 스니펫은 아직 불완전합니다.

 

logger.py  - 잘못된 코드 상태

def write_log(filename, level, msg):
    with open(filename, "a") as log_file:
        log_file.write("[{0}] {1}\n".format(level, msg))

    def critical(msg):
        write_log("CRITICAL", msg)

    def error(msg):
        write_log("ERROR", msg)

    def warn(msg):
        write_log("WARN", msg)

    def info(msg):
        write_log("INFO", msg)

    def debug(msg):
        write_log("DEBUG", msg)

 

filename 매개변수는 호출마다 변경되지 않으므로 동일한 값을 반복해서 전달하는 것은 올바른 방법이 아닙니다. 다른 함수에서 write_log 함수를 추출한 이유는 동일한 코드를 반복할 필요가 없기 때문입니다.
우리는 로깅해야 하는 로그 파일로 로거를 한 번 설정한 다음 쓸 파일 선택에 더 이상 주의를 기울이지 않고 사용하려고 합니다.

 

 

Enter the Objects

Python의 클래스를 사용하면 데이터와 함수의 논리적 그룹화를 정의할 수 있습니다. 또한 일부 컨텍스트 데이터를 로거에 추가할 수 있습니다(예: 작성하려는 파일).

 

logger_class.py

class Logger(object):
    """
    A file-based message logger with the following properties
    Attributes:
    file_name: a string representing the full path of the log file 
                to which this logger will write its messages
    """

    def __init__(self, file_name):
        """Return a Logger object whose file_name is *file_name*"""
        self.file_name = file_name

    def _write_log(self, level, msg):
        """Writes a message to the file_name for a specific Logger instance"""
        with open(self.file_name, "a", encoding='utf8') as log_file:
            log_file.write("[{0}] {1}\n".format(level, msg))

    def critical(self, level, msg):
        self._write_log("CRITICAL", msg)

    def error(self, level, msg):
        self._write_log("ERROR", msg)

    def warn(self, level, msg):
        self._write_log("WARN", msg)

    def info(self, level, msg):
        self._write_log("INFO", msg)

    def debug(self, level, msg):
        self._write_log("DEBUG", msg)


if __name__ == "__main__":
    filename = "./var/log/filename.log"
    logger = Logger(filename)
    logger._write_log('INFO', '메시지를 입력하세요!')
    logger.error('error', '에러가 발생하였습니다')

 

여기에서 많은 일이 발생하므로 이 새로운 로거가 다른 프로젝트에서 어떻게 구현되는지 보여주기 전에 잠시 코드를 살펴보십시오.

 

1. 이전 로거에서 첫 번째 큰 차이점은 class 키워드를 추가한 것입니다.

2. doc string  - class 정의 다음 """" doc string """

      확인 방법

print(logger.__doc__)

3. constructor : __init__

4. attributes and methods

5. __getattr__  / __setattr__

6. 비공개 속성(외부 프로그램에서 사용해서는 안 된다는 것을 알리는 관습) : _write_log

 

 

new_script_with_logger.py

from logger_class import Logger

if __name__ == "__main__":

    filename = "./var/log/filename.log"
    logger = Logger(filename)
    logger._write_log('INFO', '메시지를 입력하세요!')
    logger.error('error','에러가 발생하였습니다')

 

원하는 것은 이미 생성한 동일한 로거(있는 경우)를 가져오거나 이미 존재하지 않는 경우 새 로거를 생성하는 방법입니다.

 

singleton_object.py

class SingletonObject:
    class __SingletonObject:
        def __init__(self):
            self.val = None
            
        def __str__(self):
            return "{0!r} {1}".format(self,self.val)
    
    instance = None    
    def __new__(cls):
        if not SingletonObject.instance:
            SingletonObject.instance = SingletonObject.__SingletonObject()
        return SingletonObject.instance
        
    def __getattr__(self, name):
        return getattr(SingletonObject.instance, name)

    def __setattr__(self, name,value):
        return setattr(SingletonObject.instance, name, value)

 

코드를 살펴보고 이로 인해 클래스의 단일 인스턴스가 생성되거나 클래스가 인스턴스화될 때마다 반환되는 결과를 살펴보겠습니다.

1. class __SingletonObject():

    - 방안의 코끼리는 SingletonObject 클래스 내부의 클래스 정의입니다.

    - 앞의 밑줄은 다른 프로그래머에게 이 클래스가 개인 클래스이며

      원래 클래스 정의 외부에서 사용해서는 안 된다는 것을 알려줍니다. 

      개인 클래스는 로거의 기능을 구현하는 곳이며, 이 장의 끝에서 연습으로 남겨둡니다. 

    -  print 문에서 객체를 사용할 때 호출되는 __str__메서드

2. instance = None : class 속성 정의(모든 class에서 공유)

3. def __new__(cls) :  __new__ 함수의 정의에 self 매개변수가 없다는 것입니다.

    이것은 __new__가 클래스 메서드이기 때문입니다.

    object를 매개 변수로 사용하는 대신 class를 매개 변수로 받은 다음 클래스 정의를 사용하여 클래스의 새 인스턴스를

    구성합니다.

4. __getattr__ 및 __setattr__ 함수 :

    self.val 속성은 스크립트가 개체를 두 번 이상 인스턴스화하려고 시도하더라도

    개체가 동일하게 유지됨을 보여주기 위해 설정됩니다.

 

이제 이 클래스를 사용하여 싱글톤 구현이 예상대로 작동하는지 확인할 수 있습니다.

 

 

test_singleton.py

from singleton_object import SingletonObject

obj1 = SingletonObject()

obj1.val = "Object value 1"
print("print obj1: ", obj1)

print("-----")
obj2 = SingletonObject()
obj2.val = "Object value 2"
print("print obj1: ", obj1)
print("print obj2: ", obj2)


Here is the output:

print obj1:  <__main__.SingletonObject.__SingletonObject object at 0x000001B3E8032D70> Object value 1
-----
print obj1:  <__main__.SingletonObject.__SingletonObject object at 0x000001B3E8032D70> Object value 2
print obj2:  <__main__.SingletonObject.__SingletonObject object at 0x000001B3E8032D70> Object value 2

 

싱글톤 패턴에 대한 주된 비판은 프로그램을 작성할 때 피하고 싶은 것 중 하나인 전역 상태를 얻는 좋은 방법일 뿐이라는 것입니다. 전역 상태를 피하려는 이유 중 하나는 프로젝트의 한 부분에 있는 코드가 전역 상태를 변경하고 완전히 관련 없는 코드 부분에서 예기치 않은 결과를 초래할 수 있기 때문입니다.

 

 

There you have it you have discovered your first design pattern, which is called the Singleton Pattern. Congratulations!

Exercises

  • Implement your own logger singleton.
  • How would you create a logger using the singleton pattern?

(Credit: Inspiration for the singleton pattern code template was sparked by the singleton pattern implementation on http://python-3-patterns-idioms-test. readthedocs.io/en/latest/Singleton.html CC-BY-SA.)

'Practical Python Design Patterns' 카테고리의 다른 글

Builder Pattern  (0) 2023.03.28
Factory Pattern  (0) 2023.03.28
The Prototype Pattern  (0) 2023.03.28
Before We Begin  (0) 2023.03.28
Index  (0) 2023.03.28

댓글