language/Python

[Python] closure(클로저)와 Decorator(데코레이터), 로깅(logging) 개념과 예제

스파이디웹 2022. 2. 28. 17:27
728x90

1. closure(클로저)란?

내부 함수를 결과로 반환할 때, 그 내부 함수를 closure라고 한다.

 

사용 되는 곳

  • 콜백 함수에 사용
  • 지연된 평가
  • 데코레이터 함수
def add(x, y):
    def do_add():
        print('Adding', x, y)
        return x + y
    return do_add
do_add()함수가 closure함수

클로저는 나중에 함수가 올바로 작동하는 데 필요한 모든 변수의 값을 유지한다.
클로저를 함수와 그 함수가 의존하는 변숫값을 저장하는 환경이 합쳐진 것으로 생각할 수 있다.

closure를 함수로 구현

class Outer:
    def __init__(self, num):
        self.num = num
    def __call__(self):
        print(self.num)
  • Outer 클래스에 대한 객체를 생성하고 이를 호출
  • 클래스에 __call__ 메서드를 정의해두면 '객체( )'와 같이 사용할 때 __call__ 메서드가 호출
  • 사실 함수도 클래스의 객체이며 우리가 함수를 호출할 때 '함수이름( )'과 같이 사용할 수 있었던 이유가 바로 __call__ 메서드 덕분

2. decorator(데코레이터)란?

데코레이터를 설명하기전에 logging이라는 것에 대해 잠깐 설명하고 데코레이터를 정리하겠습니다.

logging이란 프로그램을 작성할 때 간단히 함수 호출 상태나 함수의 리턴 값, 함수의 처리시간 등을 확인하기 위해 print를 사용하는 것.
def add(x, y):
    return x + y

#로깅 추가
def add(x, y):
    print('Calling add')
    return x + y
    
def sub(x, y):
    print('Calling sub')
    return x - y

위와 같은 함수에 로깅이 추가 되었을 때 코드가 반복되는 일이 발생합니다.

따라서 함수에 로깅을 추가해주는 wrapper함수를 closure함수로 만듭니다.

def logged(func):
    def wrapper(*args, **kwargs):
        print('Calling', func.__name__)
        return func(*args, **kwargs)
    return wrapper
    
def add(x, y):
    return x + y

logged_add = logged(add)

print(logged_add(3,4))
#Calling add 출력
#7 출력
wrapper는 다른 함수를 감싸는 함수이다.

데코레이터는 함수 주변에 래퍼를 두는 전용 구문이라고 생각하면 됩니다.

아래의 코드는 위의 코드와 동일하게 작동합니다.

def logged(func):
    def wrapper(*args, **kwargs):
        print('Calling', func.__name__)
        return func(*args, **kwargs)
    return wrapper
    
@logged
def add(x, y):
    return x + y
    
print(add(3,4))
#Calling add 출력
#7 출력

@logged와 같은 문법을 함수를 데코레이트 한다 라고합니다.


logging을 편하게 해주는 logging 모듈

파이썬의 logging 모듈을 사용하면 print 함수를 사용하거나 파일 입출력을 사용하는 것보다 편리하게 로깅할 수 있습니다.

import logging

def hap(a, b):
    ret = a + b
    logging.info(f"input: {a} {b}, output={ret}")#print 대신에 logging.info()사용
    return ret
#logging.info()의 인자는 문자열 타입이어야 함
result = hap(3, 4)

#출력결과는 없다(logging.info가 출력되지 않는다.)
  • 로깅 모듈에는 다음과 같이 로깅 레벨이 존재
  • 로깅 모듈의 기본 레벨은 WARNING으로 설정
  • info 함수는 설정 레벨인 WARNING 보다 높으므로 해당 메세지가 출력되지 않는 것

 

레벨 로깅함수 사용할 때
DEBUG debug() 상세한 정보를 출력
INFO info() 예상대로 작동하는지를 확인
WARNING warning() 소프트웨어는 정상 동작하는데 예상치 못한 일이 발생한 것에 대해 표시
ERROR error() 소프트웨어의 일부가 정상적으로 동작하지 않는 경우에 대해 표시
CRITICAL critical() 심각한 에러 상황에 대해 표시
import logging

logging.basicConfig(level=logging.INFO)

def hap(a, b):
    ret = a + b
    logging.info(f"input: {a} {b}, output={ret}")
    return ret

result = hap(3, 4)

#INFO:root:input: 3 4, output=7 와 같이 출력

다음과 같이 로거 객체를 만들어 사용할 수도 있습니다.

log = logging.getLogger(__name__)

log.critical(message [, args])
log.error(message [, args])
log.warning(message [, args])
log.info(message [, args])
log.debug(message [, args])

3. 데코레이트 된 메소드

메소드 정의와 함께 사용할 수 있는 대표적인 빌트인(미리 정의된) 데코레이션이 몇 가지 있다.

 

1) 정적 메소드

  • @staticmethod는 정적클래스 메소드를 정의하는 데 사용
  • 정적 메소드는 클래스에 속한 함수이지만 인스턴스에 대해 연산을 하지 않음
  • 클래스를 위한 내부 지원 코드를 구현하는 데 정적 메소드를 종종 사용
  • 예) 생성한 인스턴스를 관리하는 코드(메모리 관리, 시스템 자원, 지속성, 잠금 등)
class Foo(object):
    @staticmethod
    def bar(x):
        print('x =', x)

>>> Foo.bar(2)
x=2

2) 클래스 메소드

  • @classmethod는 클래스 메소드를 정의하는 데 사용
  • 인스턴스 대신 class 객체를 첫 번째 매개변수로 받음
  • 생성자(constructor)의 대안으로 클래스 메소드를 종종 사용
  • 상속과 같은 문제에 있어서 활용됨
class Foo:
    def bar(self):
        print(self)

    @classmethod
    def spam(cls):
        print(cls)

>>> f = Foo()
>>> f.bar()
<__main__.Foo object at 0x971690>   # 인스턴스 `f`
>>> Foo.spam()
<class '__main__.Foo'>              # `Foo` 클래스

############################
class Date:
    def __init__(self,year,month,day):
        self.year = year
        self.month = month
        self.day = day

    @classmethod
    def today(cls):
        # 클래스가 어떻게 인자로 전달되는지 보라
        tm = time.localtime()
        # 새 인스턴스를 생성하는 데 사용됨
        return cls(tm.tm_year, tm.tm_mon, tm.tm_mday)

d = Date.today()

3) Property

  • @property는 메소드를 마치 속성처럼 사용할 수 있게 해줌
  • 함수를 호출하는 의믜인 ()를 붙이지 않고 변수처럼 사용할 수 있게 됨
class Car:
    def __init__(self, model):
        self.model = model

    @property
    def get_model(self):
        return self.model


c = Car("GV80")
print(c.get_model)          # c.get_model()이 아님
#GV80출력

 

참조:

https://wikidocs.net/84428

https://wikidocs.net/84430

728x90