본문 바로가기
Language & Tool/Python

🐍함수의 도구화, functools

by 박수무당벌레 2025. 6. 15.

관련글 :

operator 모듈에 대하여 https://waydd.tistory.com/63

 

 

1. 핵심 개념

  • 함수를 더 편리하게 다루기 위한 유틸리티 함수들을 모아 놓은 표준 라이브러리 모듈
  • 함수형 프로그래밍(functional programming)을 지원
  • 함수 자체를 조작 또는 함수의 동작을 수정/보완(개선)하거나, 캐싱 등 최적화할 수 있는 도구들 → 함수를 다루는 함수들(functional tools)
import functools
from functools import partial # 예시

 

 

2. 대표 함수 목록

 

- reduce

  • 누적 계산 또는 축약 이라는 뜻으로, 시퀸스(iterable)를 요소로 받아 이진 함수를 사용누적 연산 수행 (리스트 → 하나의 값으로 축소 = reduce)
    →  시퀸스 요소 왼쪽부터 차례로 누적으로 적용, sum()의 확장
  • function : 누적하는(= 하나의 값으로 반환) 이항 함수(= 두개의 인자를 받음)
  • 시작값(initializer) 인자 생략 가능, 지정 시 첫 번째 누적값으로 사용
  • 주로 operator 모듈이 제공하는 두개의 인자를 받는 연산자 함수와 함께 사용 ex)  addmuleqlt(less than, <를 의미)
표현식 : 누적 객체(iterator) = functools.reduce(function, iterable[, initializer])
# 예시
reduce(lambda x, y: x+y, [1,2,3]) # 6

 

- partial

  • 함수의 일부 인자를 미리 고정시킨 새로운 함수 생성
  • 콜백 함수, 반복문 내 함수 인자 고정 등 에 사용
표현식 : functools.partial(function, *args, **kwargs)
# 예시
from functools import partial

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

double = partial(multiply, 2)  # multiply 함수의 첫 번째 인자 x에 2를 고정한 새로운 함수
print(double(5))  # multiply(2, 5) → 10

 

- lru_cache

  • 함수 결과를 캐싱하여 성능 향상 (메모이제이션)
    →→  똑같은 입력값을 주면 항상 같은 결과가 나오는 함수라면, 그 결과를 저장해놨다가, 나중에 똑같은 입력이 들어오면 계산하지 않고 저장된 결과를 꺼내는 방식
  • 내부적으로 Least Recently Used (LRU) 알고리즘, dict + 이중 연결 리스트의 조합으로 설계
    (dict만으론 부족, Python 3.6 이전에는 dict가 순서를 유지하지 않았기 때문)
  • typed=True로 설정하면, 값뿐 아니라 타입도 구분 ex) 1과 1.0은 다른 타입
  • 중복 계산을 하지 않아 매우 빠름
  • 재귀함수에 자주 사용됨
표현식 : functools.lru_cache(maxsize=128[, typed=False])
# 예시
from functools import lru_cache

@lru_cache(maxsize=1000)  # 캐시 크기 제한 (1000개 저장)
def fib(n):
    if n <= 1:
        return n
    return fib(n-1) + fib(n-2)

 

- cache

  • 동일한 인자로 호출된 결과를 자동으로 저장하고 재사용
    → lru_cache의 maxsize 무제한 버전 (Python 3.9+)
  • 최근 사용된 순서를 유지하기 위해 내부적으로 딕셔너리 기반으로 결과 저장
  • 재귀 함수 최적화에 특히 유용
  • 메모리 무제한 사용 주의
# 예시
from functools import cache

@cache
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

print(fib(100))  # 빠르게 계산됨

 

- wraps

  • 감싼 함수(wrapper) 안에 원래 함수의 이름, 설명, 기타 메타정보를 그대로 복사해서 유지시켜줌
    (= 데코레이터 함수에서 원래 함수의 이름/문서 유지)
  • 사용자 정의 데코레이터 만들 때 필수
표현식 : functools.wraps(wrapped)
※ decorator (데코레이터)
- 기존 함수를 수정하지 않고 함수를 감싸서 기능을 추가하거나 바꾸는 문법
- 함수를 감싸는 함수
※ 추가설명) wraps를 쓰지 않은 경우와 비교 (왜 사용하는지)
①  wraps를 사용하지 않은 경우
def my_decorator(func):
    def wrapper():
        print("실행 전")
        func()
        print("실행 후")
    return wrapper

@my_decorator
def greet():
    print("안녕하세요")

greet()​

- 위에서 @my_decorator 는 greet = my_decorator(greet) 로 바꾼 것인데, greet 라는 이름은 이제 wrapper() 함수를 가리킴
  → greet()를 실행하면 wrapper()가 실행되고, 매개변수로 func()가 greet() 대신에 실행됨
②  문제 상황 재현

print(greet.__name__)  # 출력: wrapper
print(greet.__doc__)   # 출력: None​

- 나중에 디버깅하거나 문서 생성 도구가 이 함수를 보면 원래 함수를 알 수 없음
  → 추적 어려움

③  해결 : wraps 사용
from functools import wraps

def my_decorator(func):
    @wraps(func)  # wraps를 적용
    def wrapper():
        print("실행 전")
        func()
        print("실행 후")
    return wrapper
    
print(greet.__name__)  # greet
print(greet.__doc__)   # 인사하는 함수입니다.​

 

- total_ordering

  • 클래스에 일부 비교 연산자 (최소 한 개) 만 정의해도 나머지를 자동 생성, 전체 비교 연산 지원
  • 최소 __eq__ + 하나의 비교 연산자 (__lt__, __le__, __gt__, __ge__) 필요
  • 클래스 정렬 비교 편의성 향상
# 예시
from functools import total_ordering

@total_ordering
class Student:
    def __init__(self, score):
        self.score = score

    def __eq__(self, other):
        return self.score == other.score

    def __lt__(self, other):
        return self.score < other.score

print(Student(80) > Student(70))  # True

 

- cmp_to_key

  • 비교 함수 기반 정렬을 할때, 옛날 방식의 비교 함수(cmp)를 현대 파이썬의 정렬 함수(key=...)에서 사용할 수 있도록 변환해주는 도구
  • key 함수 방식은 어떤게 더 중요한지를 정하기 위한 기준값 하나를 반환하는 반면,
    cmp는 두 값을 직접 비교해서 순서를 정함 (-1, 0, 1을 반환)
표현식 : functools.cmp_to_key(func)
from functools import cmp_to_key

def my_cmp(a, b):
    if a + b > b + a:
        return -1  # a가 먼저
    elif a + b < b + a:
        return 1   # b가 먼저
    else:
        return 0

nums = ['3', '30', '34']
sorted(nums, key=cmp_to_key(my_cmp))
# '33430'이 되도록 직접 두 값 비교