decorator

codakcodak·2023년 12월 6일
0

OOP python

목록 보기
19/19
post-thumbnail
post-custom-banner

cross-cutting-concern

  • 어플리케이션에서 모든 메서드 또는 api에 대하여 공통적인 기능이 필요할 수 있다.예를 들면 벤치마킹,로깅 또는 인증,인가와 관련된 기능들이 있다.이러한 어플리케이션을 전반적으로 관통하는 공통적인 사항들을 공통관심사항(cross-cutting concern)이라 한다.
  • 파이썬에는 이러한 공통관심사항을 수행하기 위해서는 원래의 관심 함수를 감싸는 함수를 만들어 레이어링하듯이 하는 방법이 있다.아래의 4가지 방법을 통해 관심사항을 수행해본다.

decorator with oop

import logging

# Abstract Base Class (interfaceX->추상클래스를 인터페이스 처럼 활용!)
from abc import ABC, abstractmethod
from math import sqrt
from time import perf_counter

def is_prime(number: int) -> bool:
    if number < 2:
        return False
    for element in range(2, int(sqrt(number)) + 1):
        if number % element == 0:
            return False
    return True


# abstract class AbstractComponent
class AbstractComponent(ABC):
	#1
    @abstractmethod
    def execute(self, upper_bound: int) -> int:
        pass


# class ConcreteComponent extends abstract class AbstractComponent
class ConcreteComponent(AbstractComponent):
    def execute(self, upper_bound: int) -> int:
        count = 0
        for number in range(upper_bound):
            if is_prime(number):
                count += 1
        return count

# 2
# abstract class AbstractDecorator extends abstract class AbstractComponent->추상 클래스가 추상 클래스를 상속(자바에서도 가능한 문법!)
class AbstractDecorator(AbstractComponent):
    def __init__(self, decorated: AbstractComponent) -> None:
        self._decorated = decorated

# 3
# class BenchmarkDecorator extends abstract class AbstractDecorator
class BenchmarkDecorator(AbstractDecorator):
    def execute(self, upper_bound: int) -> int:
        start_time = perf_counter()
        value = self._decorated.execute(upper_bound)
        end_time = perf_counter()
        run_time = end_time - start_time
        logging.info(
            f"Execution of {self._decorated.__class__.__name__} took {run_time:.2f} seconds."
        )
        return value

# 4
# class LoggingDecorator extends abstract class AbstractDecorator
class LoggingDecorator(AbstractDecorator):
    def execute(self, upper_bound: int) -> int:
        logging.info(f"Calling {self._decorated.__class__.__name__}")
        value = self._decorated.execute(upper_bound)
        logging.info(f"Finished {self._decorated.__class__.__name__}")
        return value


def main() -> None:
    logging.basicConfig(level=logging.INFO)
    component = ConcreteComponent()
    # 5
    benchmark_decorator = BenchmarkDecorator(component)
    logging_decorator = LoggingDecorator(benchmark_decorator)
    logging_decorator.execute(50000)


if __name__ == "__main__":
    main()

1.우리가 수행하야하는 관심사항은 AbstractComponent.execute이고 이를

ConcreteComponent.execute를 통해 구현하고 있다.

2.AbstractDecorator는 BenchmarkDecorator와 LoggingDecorator에게 생성자와 관심사항(execute)를 상속시킨다.

3.BenchmarkDecorator는 상속받은 관심사항(execute)의 수행시간을 관찰한다.

4.LoggingDecorator는 상속받은 관심사항(execute)의 로깅을 수행한다.

5.관심사항(execute)를 benchmark_decorator가 감싸며 마지막으로 logging_decorator가 이를 감싼다.

INFO:root:Calling BenchmarkDecorator
INFO:root:Execution of ConcreteComponent took 0.03 seconds.
INFO:root:Finished BenchmarkDecorator

decorator with wrapper

import logging
from math import sqrt
from time import perf_counter
from typing import Any, Callable

# 1
# func can get Any type parameter,cause we did not decide the func(관심사항(func함수)을 수행하는데 아직 func가 정해지지 않았으므로 Any으로 명시!)
def with_logging(func: Callable[..., Any]) -> Callable[..., Any]:
    def wrapper(*args: Any, **kwargs: Any) -> Any:
        logging.info(f"Calling {func.__name__}")
        value = func(*args, **kwargs)
        logging.info(f"Finished {func.__name__}")
        return value

    return wrapper

# 2
def benchmark(func: Callable[..., Any]) -> Callable[..., Any]:
    def wrapper(*args: Any, **kwargs: Any) -> Any:
        start_time = perf_counter()
        value = func(*args, **kwargs)
        end_time = perf_counter()
        run_time = end_time - start_time
        logging.info(f"Execution of {func.__name__} took {run_time:.2f} seconds.")
        return value

    return wrapper


def is_prime(number: int) -> bool:
    if number < 2:
        return False
    for element in range(2, int(sqrt(number)) + 1):
        if number % element == 0:
            return False
    return True

# 3
def count_prime_numbers(upper_bound: int) -> int:
    count = 0
    for number in range(upper_bound):
        if is_prime(number):
            count += 1
    return count

# 4
def main() -> None:
    logging.basicConfig(level=logging.INFO)
    # 4
    benchmark_wrapper = benchmark(count_prime_numbers)
    logging_wrapper = with_logging(benchmark_wrapper)
    logging_wrapper(50000)
    count_prime_numbers(50000)


if __name__ == "__main__":
    main()

1.with_logging은 관심사항(함수)의 로깅을 수행한다.

2.benchmark은 관심사항(함수)의 실행시간을 수행한다.

3.관심사항을 count_prime_numbers로 특정한다.

4.관심사항(count_prime_numbers)을 benchmark로 감싸고 이를 with_logging으로 감싼다.

INFO:root:Calling wrapper
INFO:root:Execution of count_prime_numbers took 0.03 seconds.
INFO:root:Finished wrapper

decorator

import logging
from math import sqrt
from time import perf_counter
from typing import Any, Callable


def with_logging(func: Callable[..., Any]) -> Callable[..., Any]:
    def wrapper(*args: Any, **kwargs: Any) -> Any:
        logging.info(f"Calling {func.__name__}")
        value = func(*args, **kwargs)
        logging.info(f"Finished {func.__name__}")
        return value

    return wrapper


def benchmark(func: Callable[..., Any]) -> Callable[..., Any]:
    def wrapper(*args: Any, **kwargs: Any) -> Any:
        start_time = perf_counter()
        value = func(*args, **kwargs)
        end_time = perf_counter()
        run_time = end_time - start_time
        logging.info(f"Execution of {func.__name__} took {run_time:.2f} seconds.")
        return value

    return wrapper


def is_prime(number: int) -> bool:
    if number < 2:
        return False
    for element in range(2, int(sqrt(number)) + 1):
        if number % element == 0:
            return False
    return True


# 1
# benchmark_wrapper = benchmark(count_prime_numbers)
# logging_wrapper = with_logging(benchmark_wrapper)
@with_logging
@benchmark
def count_prime_numbers(upper_bound: int) -> int:
    count = 0
    for number in range(upper_bound):
        if is_prime(number):
            count += 1
    return count


def main() -> None:
    logging.basicConfig(level=logging.INFO)
    count_prime_numbers(50000)


if __name__ == "__main__":
    main()

1.decorator with wrapper에서 관심사항을 인자로 주고 감싸는 작업을 하였지만 python에서는 @를 통해 이를 대신 수행해준다.

2.함수와 가까운 decorator로 먼저 감싼다.(benchmark->with_logging)

INFO:root:Calling wrapper
INFO:root:Execution of count_prime_numbers took 0.03 seconds.
INFO:root:Finished wrapper

decorator with functools

import logging
from math import sqrt
from time import perf_counter
from typing import Any, Callable
import functools
# 1
logger = logging.getLogger("my_app")


def with_logging(
    func: Callable[..., Any], logger: logging.Logger
) -> Callable[..., Any]:
	# 2
    @functools.wraps(func)
    def wrapper(*args: Any, **kwargs: Any) -> Any:
        logger.info(f"Calling {func.__name__}")
        value = func(*args, **kwargs)
        logger.info(f"Finished {func.__name__}")
        return value

    return wrapper


def benchmark(func: Callable[..., Any]) -> Callable[..., Any]:
    @functools.wraps(func)
    def wrapper(*args: Any, **kwargs: Any) -> Any:
        start_time = perf_counter()
        value = func(*args, **kwargs)
        end_time = perf_counter()
        run_time = end_time - start_time
        logging.info(f"Execution of {func.__name__} took {run_time:.2f} seconds.")
        return value

    return wrapper


def is_prime(number: int) -> bool:
    if number < 2:
        return False
    for element in range(2, int(sqrt(number)) + 1):
        if number % element == 0:
            return False
    return True

# 3
with_default_logging = functools.partial(with_logging, logger=logger)

# 4
@with_default_logging
@benchmark
def count_prime_numbers(upper_bound: int) -> int:
    count = 0
    for number in range(upper_bound):
        if is_prime(number):
            count += 1
    return count


def main() -> None:
    logging.basicConfig(level=logging.INFO)
    count_prime_numbers(50000)


if __name__ == "__main__":
    main()

1.decorator에 넘겨줄 인자를 정의한다.

2.functools.wraps는 원래 함수(func)의 *func_dict를 wrapper 함수에 업데이트 시켜준다.이로써 wrapper의 함수이름을 count_prime_numbers로 가져올 수 있다.

3.functools.partial는 사용할 함수의 인자(지금은 logger)가 고정되어 있을때 반복적인 수행을 편리하게 해준다.(미리 줄 인자를 매크로로 만들어준다.)

4.functools.partial로 인해 미리 인자를 고정시켜주었으므로 인자를 따로 안줘도 된다.

INFO:my_app:Calling count_prime_numbers
INFO:root:Execution of count_prime_numbers took 0.04 seconds.
INFO:my_app:Finished count_prime_numbers
profile
숲을 보는 코더
post-custom-banner

0개의 댓글