파이썬 클로저 : 개념, 사용 이유, 사용 법, 장단점

파이썬 클로저 개념, 사용 이유, 사용 법, 장단점에 대해 포스팅 합니다.

해당 개념을 이해 하기 위한 사전 개념은 파이썬 함수의 특징, 스코프 입니다. 간단한 개념이니 모르신다면 해당 포스트를 확인하시면 됩니다.

파이썬 클로저 개념

Python의 클로저는 함수가 해당 스코프 외부에서 호출되는 경우에도 자유 변수에 액세스할 수 있는 함수 개체를 나타냅니다.

클로저 개념에서 중요한 것은 두 가지 입니다. 1. 함수가 종료되어도 기억되는 자유 변수, 2.내부 함수를 반환하고 외부 함수는 변수로 할당

아래의 코드를 보면 11번 줄에서 내부 함수를 반환하는 것을 볼 수 있습니다.

13번 줄을 보면 외부 함수를 변수로 할당 하는 것을 볼 수 있습니다.

# 클로저(Closure) 사용
def closure_ex1():
    # 자유 변수
    series = []
    # 클로저 영역
    def averager(v):
        series.append(v)
        print('inner: {} / {}'.format(series, len(series)))
        return sum(series) / len(series)
    
    return averager
    
avg_closure1 = closure_ex1()

print(avg_closure1(15))
print(avg_closure1(35))
print(avg_closure1(40))

실행 결과

15~17 코드의 실행 결과를 보면 함수가 종료되었음에도 자유 변수인 series가 계속 메모리에 저장되어 그 다음 함수 호출에도 이용되는 것을 볼 수 있습니다.

inner: [15] / 1
15.0
inner: [15, 35] / 2
25.0
inner: [15, 35, 40] / 3
30.0

파이썬 클로저 사용 이유

Python 클로저는 여러 가지 이유로 사용되며 코드 설계 및 구성을 위한 강력하고 유연한 메커니즘을 제공합니다.

다음은 Python에서 클로저를 사용하는 일반적인 사용 사례와 이점입니다.

1. 상태 저장 기능

클로저는 함수 호출 사이의 상태를 유지합니다. 이는 함수를 여러 번 호출할 때 일부 정보를 보존하려는 경우 유용할 수 있습니다.

# 데이터 캡슐화 및 숨기기
def counter():
    count = 0
    def increment():
        nonlocal count
        count += 1
        return count
    return increment

counter_instance = counter()
print(counter_instance())  # Output: 1
print(counter_instance())  # Output: 2

2. 데이터 캡슐화 및 숨기기

클로저를 사용하면 함수 범위 내에서 데이터를 캡슐화하여 데이터 숨기기 또는 캡슐화 형식을 제공할 수 있습니다.

3. 데코레이터

향후 정리하겠지만, 데코레이터는 클로저의 일반적인 사용 사례입니다. 코드를 변경하지 않고도 함수의 동작을 수정하거나 확장할 수 있습니다.

파이썬 클로저 사용 법

우선 클로저의 활용법을 설명하기에 앞서 클로저에서 자유 변수를 확인하는 법에 대해서 설명합니다. 위의 counter 함수에서 자유 변수명과 자유 변수 값은 아래와 같이 확인 할 수 있습니다.

def counter():
    count = 0
    def increment():
        nonlocal count
        count += 1
        return count
    return increment

counter_instance = counter()
print(counter_instance())  # Output: 1
print(counter_instance())  # Output: 2
print('자유 변수명 확인:',counter_instance.__code__.co_freevars)
print('자유 변수값 확인:',counter_instance.__closure__[0].cell_contents)

위 코드에서 주의해야 할 점은 자유 변수에 mutable 객체를 사용하는 것이 아니라면 inner function에서 자유 변수를 nonlocal로 선언해야 한다는 것입니다.

팩토리 함수

클로저 기능을 이용해서 다양한 특수 기능의 함수를 생성하게 만들 수 있습니다. 아래는 power_factory 코드로 다양한 거듭제곱을 반환하는 함수를 생성합니다.

def power_factory(power):
    def power_function(x):
        return x ** power

    return power_function

square = power_factory(2)
cube = power_factory(3)

print(square(3))  # Output: 9 
print(cube(3))    # Output: 27 

콜백 함수

클로저 함수를 만들 때 inner function은 outer funcion 내부에서 정의하는 게 아니라 함수를 매개변수로 받게 할 수 있습니다. 그러면 함수를 수정하지 않고도 다양한 작업을 유연하게 변경 할 수 있는 콜백 함수를 만들 수 있습니다.

def perform_operation(x, y, operation):
    return operation(x, y)

def add(x, y):
    return x + y

def multiply(x, y):
    return x * y

result_add = perform_operation(3, 4, add)
result_multiply = perform_operation(3, 4, multiply)

print(result_add)        # Output: 7
print(result_multiply)   # Output: 12

파이썬 클로저 사용 시 유의 점

자유 변수로 immutable 객체를 사용 시

위에서 잠시 언급했지만 자유 변수에 mutable 객체를 사용하는 것이 아니라면 inner function에서 자유 변수를 nonlocal로 선언해야 합니다.

Python에서는 내부 함수에서 불변 객체(예: int)를 참조하면 Python은 기본적으로 이를 지역 변수로 간주합니다.

nonlocal로 선언하지 않고 해당 변수의 값을 수정하려고 하면 Python은 UnboundLocalError를 발생 시킵니다.

이 예에서 목록(x[0] += 1)을 수정하는 데에는 목록이 변경 가능하므로 nonlocal을 사용할 필요가 없습니다. 그러나 nonlocal을 사용하지 않고 정수(y += 1)를 수정하려고 하면 정수의 불변성으로 인해 오류가 발생합니다.

def outer_function():
    x = [10]  # a mutable list
    y = 10    # an immutable integer

    def inner_function_list():
        x[0] += 1  # 수정가능한 list는 nonlocal이 필요하지 않음
        print(x[0])

    def inner_function_int():
        # nonlocal을 입력하지 않는 다음 줄 코드에서 UnboundLocalError이 발생함
        y += 1
        print(y)

    return inner_function_list, inner_function_int

inner_list, inner_int = outer_function()

inner_list()  # Output: 11 
inner_int()   # UnboundLocalError

변수 바인딩 주의

변수 바인딩에 주의 해야 합니다. inner function에서 정의된 변수는 해당 함수의 로컬 범위에서 바인딩 됩니다.

즉 아래 코드에서 5번 라인이 실행 된다면 4번 줄의 x가 자유 변수 x가 아닌 inner function의 로컬 변수인 x로 접근하게 되고 이는 에러를 발생 시키게 됩니다.

def outer_function():
    x = [1, 2, 3]
    def inner_function():
        x[0] = 99      # mutable object 수정
        # x = [4, 5, 6]  # 재할당(지역 변수) 시, 에러 발생
        print(x)
    return inner_function

closure_instance = outer_function()
closure_instance()

메모리 누수

클로저가 포함된 경우 의도하지 않은 메모리 누수가 발생하지 않도록 주의해야 합니다. 클로저는 포함 범위에 대한 참조를 유지하므로 수명이 긴 클로저는 개체가 더 이상 필요하지 않은 경우에도 개체가 가비지에 수집 되지 않을 수 있습니다.

클로저 장단점

클로저의 장단점은 위의 글에서 언급되었지만, 요약하면, 장점은 캡슐화, 재사용성, 함수형 프로그래밍를 가능하게 해줍니다. 반면 단점은 변수 바인딩 주의, 메모리 사용량 증가, 복잡성 증가 및 디버깅의 어려움이 있습니다.

따라서 관련된 잠재적인 위험을 염두에 두어야 하며 이에 따라 클로저를 신중하게 사용해야 합니다.

참고하면 좋은 글

Leave a Comment

목차