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!
- 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 |
댓글