[Python 모듈] functools : 고차 함수와 관련된 도구를 제공하는 모듈

1. functools 모듈이란?

 

functools 라이브러리는 파이썬에서 고차 함수(higher-order functions)와 관련된 도구를 제공하는 모듈입니다. 고차 함수란, 다른 함수를 인수로 받거나 반환하는 함수를 말합니다. 이 모듈을 사용하면 함수와 관련된 다양한 작업을 보다 쉽게 처리할 수 있습니다.

 


2. functools.partial()

 

functools.partial() 함수는 함수의 일부 인자를 고정하여 새로운 함수를 생성합니다. 이 함수를 사용하면 기존의 함수를 재활용하면서 인자를 고정할 수 있습니다. 이 함수는 여러 개의 인자를 받는 함수와 키워드 인자를 받는 함수 모두에 사용할 수 있습니다.

 

아래 코드는 functools.partial을 사용하는 간단한 예시 코드입니다.  power() 함수는 두 개의 인자를 받아 거듭제곱 연산을 수행합니다. functools.patial()을 사용하여 기존 power함수의 exponent 인자에 고정값을 할당한 새로운 함수를 만듭니다. square함수는 exponent 값이 2로 고정된 함수로, 인자 하나를 받아 제곱을 계산합니다. cube 함수는 exponent 값이 3으로 고정된 함수로 인자 하나를 받아 세제곱을 계산합니다.

 

>>> import functools

>>> def power(base, exponent):
>>>     return base ** exponent

# functools.partial을 사용하여 기본값을 지정한 새로운 함수를 생성합니다.
>>> square = functools.partial(power, exponent=2)
>>> cube = functools.partial(power, exponent=3)

# 이제 square() 및 cube() 함수를 사용하여 값을 제곱하고 세제곱합니다.
>>> print(square(4))  # 4^2 = 16
>>> print(cube(4))    # 4^3 = 64

 


3. functools.reduce()

 

functools.reduce() 함수는 시퀀스(리스트, 튜플 등)의 모든 요소에 대해 주어진 함수를 적용하여 결과를 반환합니다. 예를 들면 리스트의 모든 값을 곱하거나 합산할 때 사용할 수 있습니다. 이 함수는 파이썬 3에서 내장 함수가 아닌 functools 모듈에서 제공됩니다.

 

아래 코드는 functools.reduce을 사용하는 간단한 코드입니다. multiply 함수는 두 개의 인자를 받아 곱셈 연산을 수행합니다. functools.reduce는 multiply 함수와 리스트 numbers를 인자로 받습니다. 그리고 리스트 내의 모든 숫자들을 차례대로 multiply함수에 적용하여 누적 곱을 계산합니다.

 

>>> import functools

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

>>> numbers = [1, 2, 3, 4, 5]

# functools.reduce()를 사용하여 숫자들을 모두 곱합니다.
>>> product = functools.reduce(multiply, numbers)

>>> print(product) # 결과: 120

 


4. functools.cache()와 functools.lru_cach()

 

functools.cache()와 functools.lru_cache()는 모두 함수의 결과를 캐싱하는 데 사용되는 데코레이터입니다. 그러나 동작 방식에 차이가 있습니다. functools.cache는 파이썬 3.9부터 사용할 수 있으며, 함수의 결과를 캐시하는 데 사용됩니다. 함수의 인자에 따른 결과가 캐시에 저장되어, 동일한 인자로 함수를 다시 호출할 경우 캐시된 결과를 반환합니다. 그러나 cache()는 캐시 크기에 제한이 없기 때문에, 메모리 사용량에 주의해야 합니다.

 

아래 코드는 functools.cache를 사용하는 간단한 코드입니다. slow_function() 이라는 시간이 오래걸리는 함수가 있습니다. 이 함수는 인자 x를 제곱한 값을 반환합니다. 그러나 실제로 time.sleep(2)을 포함하고 있기 때문에 2초가 지연됩니다. functools.cache를 사용하여 함수 결과를 캐시합니다. 그 결과로, 동일한 인자로 함수를 호출하면 캐시된 결과가 반환되고, 시간이 절약됩니다.

 

>>> import functools
>>> import time

>>> @functools.cache
>>> def slow_function(x):
>>>     time.sleep(2)  # 시간이 오래 걸리는 작업(예: 2초 지연)
>>>     return x * x

# 첫 번째 함수 호출은 시간이 오래 걸립니다.
>>> start_time = time.time()
>>> print(slow_function(3))  # 결과: 9
>>> print(f"걸린 시간: {time.time() - start_time:.2f}초")

# 두 번째 함수 호출은 캐시된 결과를 사용하여 빠릅니다.
>>> start_time = time.time()
>>> print(slow_function(3))  # 결과: 9
>>> print(f"걸린 시간: {time.time() - start_time:.2f}초")

 

functools.lru_cache() : 이 데코레이터는 Least Recently Used (LRU) 알고리즘을 사용하여 캐시 크기를 제한합니다. 캐시가 가득찬 경우, 가장 오래된 결과(가장 오랫동안 사용되지 않은 결과)가 새 결과를 대체됩니다. 이를 통해 메모리 사용량을 제한할 수 있습니다. lru_cache는 파이썬 3.2부터 사용 가능하며, 캐시 크기를 설정할 수 있는 maxsize를 인자로 받습니다.

 

아래코드는 functools.lru_cache를 사용하는 간단한 예시 코드입니다. 이 코드에서도 slow_function이라는 시간이 오래 걸리는 함수가 있습니다. 이 함수는 인자 x를 제곱한 값을 반환합니다. 그러나 실제로 time.sleep(2)을 포함하고 있기 때문에 2초가 지연됩니다. functools.lru_cache를 사용하여 함수 결과를 캐시하고 캐시 크기를 제한합니다. 예제에서 먼저 3을 호출하고, 2초 동안 기다린 후 결과를 출력합니다. 이후 다시 3을 호출하면 이번에는 캐시된 결과를 사용하여 거의 즉시 결과를 출력합니다. 그런 다음 4와 5를 호출하여 캐시를 채웁니다. 이제 캐시에는 3, 4, 5에 대한 결과가 저장되어 있습니다. 이때 2을 호출하면, 캐시가 가득차 있기 때문에 가장 오래된 결과인 3에 대한 결과가 삭제되고 다시 계산되어 반환됩니다.

 

>>> import functools
>>> import time

>>> @functools.lru_cache(maxsize=3)  # 최대 캐시 크기를 3으로 설정합니다.
>>> def slow_function(x):
...     time.sleep(2)  # 시간이 오래 걸리는 작업(예: 2초 지연)
...     return x * x

# 첫 번째 함수 호출은 시간이 오래 걸립니다.
>>> start_time = time.time()
>>> print(slow_function(3))  # 결과: 9
>>> print(f"걸린 시간: {time.time() - start_time:.2f}초")

# 두 번째 함수 호출은 캐시된 결과를 사용하여 빠릅니다.
>>> start_time = time.time()
>>> print(slow_function(3))  # 결과: 9
>>> print(f"걸린 시간: {time.time() - start_time:.2f}초")

# 새로운 인자를 사용하여 캐시를 채웁니다.
>>> slow_function(4)
>>> slow_function(5)

#  새로운 인자를 호출하면 가장 오래된 3을 지우고 캐시를 채웁니다.
>>> start_time = time.time()
>>> print(slow_function(2))  # 결과: 4
>>> print(f"걸린 시간: {time.time() - start_time:.2f}초")

 


5. functools.wraps()

 

functools.wraps() 함수는 데코레이터를 사용할 때 함수의 메타데이터(예 : 함수명, 문서 문자열 등)를 유지하기 위해 사용됩니다. 

 

아래 코드는 functools.wraps()를 사용하는 간단한 예시 코드입니다. my_decorator라는 사용자 정의 데코레이터를 만들었습니다. 이 데코레이터는 함수를 호출하기 전후에 메시지를 출력합니다. functools.wraps()는 my_decorator 내부의 wrapper 함수에 적용되어, 원래 함수 say_hello의 메타데이터를 wrapper 함수에 복사합니다.

 

>>> import functools

>>> def my_decorator(func):
...     @functools.wraps(func)
...     def wrapper(*args, **kwargs):
...         print("Something is happening before the function is called.")
...         result = func(*args, **kwargs)
...         print("Something is happening after the function is called.")
...         return result
...     return wrapper

>>> @my_decorator
... def say_hello(name):
...     """Greet someone by their name."""
...     print(f"Hello, {name}!")

>>> say_hello("John")

 

아래 코드를 실행하여 메타데이터를 유지하는지를 확인 할 수 있습니다.

>>> print(say_hello.__name__)
'say_hello'
>>> print(say_hello.__doc__)
'Greet someone by their name.'

 

 

functools.wraps()를 사용하지 않았다면, 데코레이트된 함수의 이름과 문서 문자열이 다음과 같이 wrapper함수의 정보로 변경됩니다.

>>> say_hello.__name__
'wrapper'
>>> say_hello.__doc__
None

 


6. functools.partialmethod()

 

functools.partialmethod() 함수는 functools.partial() 함수와 유사한 역할을 합니다. 그러나 적용되는 대상에 차이가 있습니다. partial함수는 일반 함수에 대해 인수를 부분 적용하는데 사용되며, functools.partialmethod는 클래스의 메서드에 대해 인수를 부분 적용하는데 사용됩니다. 

 

아래 코드는 functools.partialmethod()을 사용하는 간단한 코드입니다. Calculator라는 클래스가 정의되어 있고, 이 클래스에는 multiplay라는 메서드가 정의되어 있습니다. 이 메서드는 두 개의 인자 'x'와 'y'를 받아 곱한 값을 반환합니다. functools.partialmethod를 사용하여 multiply 메서드의 x인자를 2로 고정한 새로운 메서드 multiply_by_two를 생성합니다. 이제 multiply_by_two 메서드를 호출할 때는 y 인자만 절달하면 됩니다. 그러면 원래 multiply 메서드를 호출하는 것처럼 결과를 얻을 수 있습니다.

 

>>> import functools

>>> class Calculator:
...     def multiply(self, x, y):
...         return x * y

...     # multiply 메서드의 x 인자를 2로 고정합니다.
...     multiply_by_two = functools.partialmethod(multiply, 2)

# Calculator 클래스의 인스턴스를 생성합니다.
>>> calc = Calculator()

# multiply_by_two 메서드를 호출할 때는 y 인자만 전달하면 됩니다.
>>> result = calc.multiply_by_two(3)
>>> print(result)  # 결과: 6

 


7. functools.cmp_to_key()

 

functools.cmp_to_key() 함수는 비교 함수('cmp' 스타일)를 키 함수(key 스타일)로 변환하는데 사용됩니다. 파이썬 3부터는  정렬 함수인 sorted() 및 list.sort()가 키 함수를 사용하도록 변경됨에 따라 비교함수를 사용하는 이전 코드를 쉽게 마이그레이션할 수 있도록 도와줍니다. 

 

아래 코드는 functools.cmp_to_key를 사용하는 간단한 코드입니다.  compare_func는 비교 함수로 두 개의 인자 a와 b를 비교합니다. a가 b보다 작으면 -1, 같으면 0, 크면 1을 반환합니다. functools.cmp_to_key() 함수를 사용하여 compare_func를 키 함수로 변환하고, key_func라는 새로운 함수를 저장합니다. 그 다음 key_func를 사용하여 sorted()함수로 숫자 리스트를 정렬할 수 있습니다.

 

>>> import functools

# 비교 함수: 첫 번째 요소가 두 번째 요소보다 작으면 음수, 같으면 0, 크면 양수를 반환합니다.
>>> def compare_func(a, b):
...     if a < b:
...         return -1
...     elif a == b:
...         return 0
...     else:
...         return 1

# 비교 함수를 키 함수로 변환합니다.
>>> key_func = functools.cmp_to_key(compare_func)

# 키 함수를 사용하여 정렬합니다.
>>> numbers = [5, 2, 4, 1, 3]
>>> sorted_numbers = sorted(numbers, key=key_func)
>>> print(sorted_numbers)  # 결과: [1, 2, 3, 4, 5]