Top

Python Decoration

여러 프로그래밍 패러다임

관점지향프로그래밍 (AOP)

위키백과: 관점지향프로그래밍

컴퓨팅에서 관점 지향 프로그래밍(aspect-oriented programming, AOP)은 횡단 관심사(cross-cutting concern)의 분리를 허용함으로써 모듈성을 증가시키는 것이 목적인 프로그래밍 패러다임이다. 코드 그 자체를 수정하지 않는 대신 기존의 코드에 추가 동작(어드바이스)을 추가함으로써 수행하며, “함수의 이름이 ‘set’으로 시작하면 모든 함수 호출을 기록한다”와 같이 어느 코드가 포인트컷(pointcut) 사양을 통해 수정되는지를 따로 지정한다. 이를 통해 기능의 코드 핵심부를 어수선하게 채우지 않고도 비즈니스 로직에 핵심적이지 않은 동작들을 프로그램에 추가할 수 있게 한다. 관점 지향 프로그래밍은 관점 지향 소프트웨어 개발의 토대를 형성한다.

관점 지향 소프트웨어 개발은 온전한 엔지니어링 분야를 가리키는 반면에 관점 지향 프로그래밍은 소스 코드 레벨에서 관심사들의 모듈화를 지원하는 프로그래밍 메서드들과 도구들을 포함하고 있다.

관심 지향 프로그래밍은 프로그램 로직을 명확한 부분들(이른바 “관심사”)로 나누는 것을 수반한다. 거의 모든 프로그래밍 패러다임들은 관심사들을 별도의 독립적인 엔티티로 그룹화하고 캡슐화하는 것을 어느 정도는 지원하며, 이는 이러한 관심사들을 구현, 추상화, 합성하기 위해 사용할 수 있는 추상화(예: 함수, 프로시저, 모듈, 클래스, 메서드)를 제공함으로써 수행된다. 일부 관심사들은 프로그램 내의 여러 추상적 개념들에 영향을 미치며 이러한 형태의 구현체를 거역한다. 이러한 관심사들을 크러스 커팅 관심사(cross-cutting concerns)라고 부른다.

횡단 관심사의 전형적인 예로 로깅을 들 수 있는데 로깅 전략이 필연적으로 시스템 상에서 로그되는 모든 부분에 영향을 미치기 때문이다. 그러므로 로깅은 로그가 되는 모든 클래스들과 메서드들을 횡단한다.

모든 AOP 구현체들은 각각의 관심사를 한 자리에 캡슐화하는 횡단 표현식들을 일부 보유하고 있다. 구현체들 간의 차이점은 제공되는 생성자들의 권한, 보안, 사용성에 기인한다.

함수 추적을 위한 로깅 넣기

두개의 수를 인수로 받아 덧셈을 하는 함수, 곱셈을 하는 함수를 보자:

#!/usr/bin/python3

def sum_two_numbers(a, b):
    print("inside the sum_two_numbers function")
    return a + b

def multiply_two_numbers(a, b):
    print("inside the multiply_two_numbers function")
    return a * b

a, b = 2, 3

# getting the value through return of the function
print("Sum =", sum_two_numbers(a, b))
print("Multiply =", multiply_two_numbers(a, b))

결과는 이럴 것이다:

inside the sum_two_numbers function
Sum = 5
inside the multiply_two_numbers function
Multiply = 6

우리는 함수에 추적을 위한 로그를 넣고 싶다. 아래와 같을 것이다:

#!/usr/bin/python3

def sum_two_numbers(a, b):
    print("before operation")
    print("inside the sum_two_numbers function")
    result = a + b
	print("after operation")
    return result

def multiply_two_numbers(a, b):
    print("before operation")
    print("inside the multiply_two_numbers function")
    result = a * b
	print("after operation")
    return result

a, b = 2, 3

# getting the value through return of the function
print("Sum =", sum_two_numbers(a, b))
print("Multiply =", multiply_two_numbers(a, b))

결과는 이럴 것이다.

before operation
inside the sum_two_numbers function
after operation
Sum = 5
before operation
inside the multiply_two_numbers function
after operation
Multiply = 6

함수 실행 과정을 추적하기 위해 그 함수에 두개의 print를 넣었고, 임시로 저장할 변수도 추가 했다. 함수가 바뀌어 버렸다. 게다가 로그를 넣을 함수가 수십개라면?

여기서 관점지행 프로그래밍을 사용할 수 있다. 파이썬은 데코레이터를 통해 관점지향프로그래밍을 할 수 있다.

함수 데코레이션

함수를 위한 데코레이션은 간단하다. 함수를 파라미터로 받는 함수를 만들고 내부 함수에 인자를 전달하면 된다:

#!/usr/bin/python3
def hello_decorator(func):
    def inner1(*args, **kwargs):
        print("before operation")
        returned_value = func(*args, **kwargs)
        print("after operation")
        return returned_value
    return inner1

@hello_decorator
def sum_two_numbers(a, b):
    print("inside the sum_two_numbers function")
    return a + b

@hello_decorator
def multiply_two_numbers(a, b):
    print("inside the multiply_two_numbers function")
    return a + b


a, b = 1, 2

# getting the value through return of the function
print("Sum =", sum_two_numbers(a, b))

결과는 동일하다:

before operation
inside the sum_two_numbers function
after operation
Sum = 5
before operation
inside the multiply_two_numbers function
after operation
Multiply = 6

데코레이션 순서

다수의 데코레이터가 적용된 경우 순서에 따라 결과가 다르므로 주의가 필요하다. 아래에서 decor1은 제곱을 하고, decor2는 두배를 하는 데코레이터다:

# code for testing decorator chaining
def decor1(func):
    def inner():
        x = func()
        return x * x
    return inner
 
def decor2(func):
    def inner():
        x = func()
        return 2 * x
    return inner
 
@decor1
@decor2
def num1():
    return 10
 
@decor2
@decor1
def num2():
    return 10
   
print(num1())
print(num2())

결과:

400
200

클래스 데코레이션:

#!/usr/local/bin/python
from datetime import datetime

class Timestamp:
    def __init__(self, f):
        self.func = f

    def __call__(self, *args, **kwargs):
        print('START:', datetime.now())
        self.func(*args, **kwargs)
        print('END:', datetime.now())

생성자에서 함수를 인자로 받아 인스턴스 변수에 저장하고 __call__에서 적용한다.

사용하는 예:

class MainClass:
    @Timestamp
    def fun1():
        print("FUNCTION 1")
    @Timestamp
    def fun2():
        print("FUNCTION 2")
    @Timestamp
    def fun3():
        print("FUNCTION 3")

i = MainClass()
i.fun1()
i.fun2()
i.fun3()

결과 출력:

START: 2023-03-25 15:08:52.608531
FUNCTION 1
END: 2023-03-25 15:08:52.608581
START: 2023-03-25 15:08:52.608587
FUNCTION 2
END: 2023-03-25 15:08:52.608593
START: 2023-03-25 15:08:52.608596
FUNCTION 3
END: 2023-03-25 15:08:52.608602

참조