클로저와 데코레이터

현서·2025년 1월 3일

파이썬

목록 보기
25/27
post-thumbnail

1. 클로저(Closure)

내부 함수가 자신을 감싸고 있는 외부 함수의 변수에 접근할 수 있는 특성을 가진다.
외부 함수가 호출된 후에도 그 변수의 상태를 기억하고 사용할 수 있는 기능이다.
주로 데이터 은닉과 상태 유지에 활용된다.

def mul2(n):
   return n * 2
print(mul2(10))
print(mul2(5))
20
10
def mul5(n):
    return n * 5
print(mul5(10))
print(mul5(5))
50
25

위에 코드를 보면 계속 mul2, mul5를 만들어서 실행하고 있는데,
원하는 숫자를 곱할 때마다 계속 mul1, mul2, mul3 ... mul100 이런 식으로 만들어야 하는 단점이 있다...


바로 밑에 클래스 + 일반 메서드 방식의 코드를 보자.

class Mul:
    def __init__(self, m):
        self.m = m

    def mul(self, n):
        return self.m * n
mul2 = Mul(2)
print(mul2.mul(10))
print(mul2.mul(5))

mul5 = Mul(5)
print(mul5.mul(10))
print(mul5.mul(5))
20
10
50
25

위의 클래스 + 일반 메서드 방식의 단점은

  1. 메서드 호출이 길다.
    → mul2.mul(10)처럼 .mul()을 매번 붙여야 한다. 간결하지 않음.

  2. 함수처럼 쓰기 어렵다.
    → mul2(10)처럼 직접 호출(callable) 할 수 없음. 함수처럼 쓰려면 .mul() 메서드 이름을 반드시 알아야 함.

  3. 함수의 개념과 멀다.
    → 곱하기 2라는 간단한 개념을 구현하기 위해 클래스 정의와 인스턴스를 만드는 건 복잡한 구조일 수 있음.


그렇다면 바로 밑의 클래스 + __call__ 오버라이딩 방식을 보자.

class Mul:
    def __init__(self, m):
        print('생성자 호출')
        self.m = m

    def __call__(self, n): # 오버라이딩. 객체를 함수처럼 쓸 수 있음.
        print('call 호출')
        return self.m * n

mul2 = Mul(2)
print(mul2(10))

mul5 = Mul(5)
print(mul5(10))
생성자 호출
call 호출
20
생성자 호출
call 호출
50

위의 클래스 + __call__ 오버라이딩 방식 의 단점은

  1. 불필요한 클래스 구조
    → 단순히 곱하기 기능을 만들기 위해 init, call을 갖춘 클래스를 만드는 것은 과함.

  2. 메모리 오버헤드
    → 객체를 생성하면 내부적으로 더 많은 메모리/구조가 생기므로 단순 함수형보다 리소스 낭비.

  3. 의미 전달이 불명확할 수 있음
    → mul2(10)처럼 함수처럼 동작하지만 사실은 클래스 인스턴스라, 다른 사람이 코드를 볼 때 혼동될 수 있음.


그러면, 간결하고 직관적인 클로저 방식의 코드를 보자.

# 클로저 사용하기
def mul(m):
    def wrapper(n):
        return m * n
    return wrapper

mul2 = mul(2) # 내부 함수를 리턴
print(mul2(10)) # 내부 함수를 이용해서 10을 곱함.

mul5 = mul(5)
print(mul5(10))
20
50

여기서 내부 함수를 리턴한다는 것은,

def wrapper(n):
        return m * n

이 내부 함수를 리턴한다는 것.

그래서 만약 mul2 = mul(2) 이런 코드라면

def wrapper(2):
        return m * 2

이렇게 됨.
그다음 print(mul2(10)) 이면, 10 * 2 = 20
20 출력!


2. 데코레이터(Decorator)

기존 함수를 수정하거나 확장하는 함수.
다른 함수를 인자로 받아 새로운 함수를 반환하여 원래 함수의 기능을 변경하거나 추가하는 데 사용되며, 주로 코드의 재사용성과 가독성을 높이는 데 활용된다.

import time
def func1(a, b):
    start = time.time()
    print('함수가 시작되었습니다')

    result = a + b

    end = time.time()
    print(f'함수 수행시간: {end - start}')
    return result
result = func1(10, 3)
print(result)
함수가 시작되었습니다
함수 수행시간: 0.001520395278930664
13

함수 수행시간을 더 잘 확인하기 위해 다음 코드를 보자.

print(format(5.984306335449219e-05, ".10f"))
0.0000598431

이 코드를 통해 아주 작은 부동소수점(소수점 위치가 자유롭게 움직일 수 있는 실수 표현 방식) 숫자를 소수점 아래 10자리까지 고정 소수점 형태로 출력한다.

def func2(a, b):
    start = time.time()
    print('함수가 시작되었습니다')

    result = a * b

    end = time.time()
    print(f'함수 수행시간: {end - start}')
    return result
result = func2(10, 3)
print(result)
함수가 시작되었습니다
함수 수행시간: 0.0008776187896728516
30

위 코드들의 단점

  • 이 함수 안에서 직접 타이머와 로그를 작성하고 있어서, 다른 함수에도 동일하게 적용하려면 복붙이 필요함.

  • 유지보수가 어려워짐. 나중에 로깅 방식이 바뀌면 모든 함수에서 수정을 해야 함.


그러면 바로 밑의 데코레이터 방식 코드를 보자.

# 데코레이터 만들기
def func1(a, b):
    result = a + b
    return result

def func2(a, b):
    result = a * b
    return result
def elapsed(func):
    def wrapper(a, b):
        start = time.time()
        print('함수가 시작되었습니다')

        result = func(a, b)

        end = time.time()
        print(f'함수 수행시간: {end - start}')
        return result
    return wrapper
deco1 = elapsed(func1) # 내부 함수를 리턴
result = deco1(10, 3)
print(result)
함수가 시작되었습니다
함수 수행시간: 0.003779172897338867
13
deco2 = elapsed(func2)
result = deco2(10, 3)
print(result)
함수가 시작되었습니다
함수 수행시간: 0.006906270980834961
30
@elapsed
def func1(a, b):
    result = a + b
    return result

print(func1(10, 3))
함수가 시작되었습니다
함수 수행시간: 9.942054748535156e-05
13

@elapsed 부분이 데코레이터 문법이다.

@elapsed는 func1 = elapsed(func1)과 같은 의미여서,
func1()이 호출될 때 실제로는 elapsed(func1)이 호출되어 wrapper() 함수가 실행된다.

@elapsed
def func2(a, b):
    result = a * b
    return result

print(func2(10, 3))
함수가 시작되었습니다
함수 수행시간: 0.0006098747253417969
30

데코레이터의 장점

  1. 코드 중복 제거
  • 시간 측정, 로깅, 인증 등 공통 작업을 따로 분리해서 재사용할 수 있다.
  1. 가독성 향상
  • @데코레이터명으로 직관적으로 기능을 확장했음을 알 수 있다.
  1. 관심사 분리 (Separation of Concerns)
  • 핵심 기능과 부가 기능(로깅, 타이밍 등)을 분리해서 관리 가능.
profile
The light shines in the darkness.

0개의 댓글