본문 바로가기

미래 먹거리를 위하여

[파이썬 정복하기] 라이브러리 5장 - 함수형 프로그래밍 다루기2 (맥OS 기준)

OS: MAC

참고: 👉 점프 투 파이썬 - 라이브러리 예제 바로가기

029. 순서대로 좌표를 정렬하려면?―functools.cmp_to_key(캄프투키)

- 직접 만든 비교 함수를 정렬 키 함수로 바꿔주는 도구. 좌표, 다중 조건, 사용자 정의 객체 정렬에서 매우 유용.

구분 내용 예시 결과
모듈 고차 함수(함수를 다루는 함수)를
담은 내장 라이브러리
import functools(펑터스)
기능
요약
비교 함수(cmp)를
정렬 키 함수(key)
로 변환해 복잡한 정렬 기준 구현
sorted(iterable, key=functools.cmp_to_key(compare_func))

# 음수: 첫 번째 값이 더 작다 → 앞으로, 0 : 두 값이 같다.
양수: 첫 번째 값이 더 크다 → 뒤로
예시① 좌표를 (x, y) 순서로 오름차순 정렬 import functools
def compare(p1, p2):
   if p1[0] != p2[0]:
         return p1[0] - p2[0]
   else:
   return p1[1] - p2[1]

points = [(3,4),(1,2),(3,2),(2,5)]
result = sorted(points, key=functools.cmp_to_key(compare))
print(result)
[(1, 2), (2, 5), (3, 2), (3, 4)]
예시② 숫자를 큰 순서대로(내림차순) 정렬 import functools
def compare(a, b):
         return b - a
nums = [5,2,9,1,7]
result = sorted(nums, key=functools.cmp_to_key(compare))
print(result)
[9, 7, 5, 2, 1]
예시③ 문자열 길이 기준으로 정렬 import functools
def compare_len(a, b):
       return len(a) - len(b)
words = ['apple','kiwi','banana','pear']
result = sorted(words, key=functools.cmp_to_key(compare_len))
print(result)
['kiwi', 'pear', 'apple', 'banana']

라이브러리 예제 문제: 

점프 투 파이썬 - 라이브러리 예제 편 5장 29번 문제
점프 투 파이썬 - 라이브러리 예제 편 5장 29번 문제 해석 및 결과
점프 투 파이썬 - 라이브러리 예제 편 5장 29번 문제 해석 및 결과 2(key=lambda x 함수 사용)

030. 웹 페이지를 임시로 저장하려면? ― functools.lru_cache

- lru_cache는 같은 인자에 대한 함수 결과를 자동 저장해, 다음 호출을 즉시 반환하게 하는 초간단 성능 부스트 데코레이터.

 

  • I/O(웹, DB) 처럼 느린 작업 결과를 인자값만으로 결정할 수 있으면 효과가 큼.
  • 결과가 자주 바뀌는 API라면: maxsize를 작게, 주기적으로 cache_clear() 호출 or 응답에 버전/타임스탬프를 인자로 포함해 캐시 갱신 제어.
  • 입력이 리스트·딕셔너리처럼 해시 불가 타입이면 튜플/프리즈딕트 등 해시 가능한 형태로 변환해 인자로 넘겨야 함.

 

구분 내용 예시 결과
모듈 표준 라이브러리의 메모이제이션(결과 캐싱) 데코레이터 from functools(펑터스) import lru_cache(엘-알-유 캐시)
기본
문법
함수 호출 결과를
인자값 기준으로 캐시
from functools import lru_cache
@lru_cache()
def get(url):   # 실제론 requests.get(url).text 등
     return f"{url}의 HTML"
같은 url로 다시 호출하면
계산/요청 생략,
캐시에서 즉시 반환
웹 요청
캐싱
(모의)
최초만 “느림”,
이후 “빠름”
import time
from functools import lru_cache
@lru_cache(maxsize=128)
def fetch(url):
     time.sleep(1) # 느린 I/O 가정
           return f"DATA: #실제 url 주소 입력 {url}"
fetch("a") # 1초
fetch("a") # 즉시(캐시)
첫 호출: 약 1초 ⏳
→ 이후 동일 인자: 즉시 ⚡
maxsize 캐시 크기(항목 수) 제한.
초과 시 LRU부터 삭제
@lru_cache(maxsize=2) 3개 키를 번갈아 호출하면 가장 오래 안 쓴 항목부터
제거
typed 1과 1.0을
다른 인자로 취급
@lru_cache(typed=True) f(1)과 f(1.0)이 별도로 캐시됨
캐시 상태 확인 적중/미스/현재용량
등 통계
info = fetch.cache_info()
# CacheInfo(hits=1, misses=1, maxsize=128, currsize=1)
적중/미스 수를
숫자로 확인 가능
캐시
비우기
특정 함수의 캐시 전부 삭제 fetch.cache_clear() 다음 호출은
다시 계산/요청 수행
키 포인트 순수 함수(부작용/외부상태 의존 적음)에 적합 요청 결과가
자주 재사용될 때 성능 큰 폭 개선

라이브러리 예제 문제: 

점프 투 파이썬 - 라이브러리 예제 편 5장 30번 문제
점프 투 파이썬 - 라이브러리 예제 편 5장 30번 문제 해석 및 결과

031. 기존 함수로 새로운 함수를 만들려면? ― functools.partial(파셜)

- 함수 일부 인자를 고정해 새 함수로 만드는 도구

- 기본값을 매번 반복해서 전달하지 않아도 됨. 콜백 함수, GUI 버튼, 스레드 등에 자주 활용됨

구분 내용 예시 결과
모듈 기존 함수를 “부분 적용”하여
새로운 함수 생성
import functools(펑터스)
기능
요약
일부 인수를 미리 고정한 새 함수를 만들 때 사용
→ 매번 같은 인수를 넣지 않아도 됨
functools.partial(함수명, 고정인자)
기본
문법
functools.partial
(func, *args, **kwargs)
기존 함수 func의 일부 인자를 미리 지정해 고정시킴
double = functools.partial(pow, exp=2)
double(3)
9
예시① 숫자를 제곱하는 함수 미리 정의 import functools
pow2 = functools.partial(pow, exp=2)
print(pow2(5))
25
예시② 문자열 왼쪽 정렬 함수 import functools
align_left = lambda s: s.ljust(10, '*')
print(align_left('Hi'))
Hi********
예시③ 웹 요청 기본 주소 고정 import functools
fetch = functools.partial(print, "요청 URL:")
fetch("https://example.com")
요청 URL: https://example.com

라이브러리 예제 문제: 

점프 투 파이썬 - 라이브러리 예제 편 5장 31번 문제
점프 투 파이썬 - 라이브러리 예제 편 5장 31번 문제 해석 및 결과

032. 함수를 적용하여 하나의 값으로 줄이려면? ― functools.reduce

- 반복 가능한 데이터를 누적해서 하나의 값으로 만든다

항목 설명 예시 결과
기능 요약 여러 개의 값을
누적 계산하여
하나의 값으로 축약
(reduce)
하는 함수
reduce(함수, iterable[, 초기값]) -
모듈 위치 functools 모듈 안에 포함 from functools import reduce -
필수 인자 - 함수: 누적 계산에 사용할 함수
- iterable: 리스트, 튜플 등
반복 가능한 객체
from functools import reduce
nums = [1, 2, 3, 4, 5]
① result = reduce(lambda x, y: x + y, nums)
    print(result)
② result2 = reduce(lambda x, y: x * y, nums)
     print(result2)

# reduce(add, seq) → 누적 덧셈
   reduce(mul, seq) → 누적 곱셈
 15
② 120
선택 인자 초기값: 누적 연산의
시작값을 지정할 수 있음
(없으면 첫 요소부터 시작)
from functools import reduce
reduce(lambda x, y: x + y, [1, 2, 3, 4, 5], 10)
25
동작 과정 [1, 2, 3, 4, 5]
→ (((1+2)+3)+4)+5 식으로
하나로 줄여감
lambda x, y: x + y -
대표 활용 예시 문자열 연결 from functools import reduce
w = ['ChatGPT', 'makes', 'coding', 'fun!']
s = reduce(lambda x, y: x + ' ' + y, w)
print(s)

# ''.join(iterable)도 자주 쓰이는 문자열 연결 방법
예) ''.join(['a', 'b', 'c']) > 'abc'
ChatGPT makes coding fun!
같이 쓰면 좋은 함수 operator 모듈의 함수
(add, mul 등)
from functools import reduce
from operator import add, mul

 print(reduce(add, [10, 20, 30, 40]))
② print(reduce(mul, [2, 3, 4, 5]))  # 곱
③ print(reduce(add, [10, 20, 30], 100))
100
② 120
③ 160

라이브러리 예제 문제: 

점프 투 파이썬 - 라이브러리 예제 편 5장 32번 문제
점프 투 파이썬 - 라이브러리 예제 편 5장 32번 문제 해석 및 결과

033. 래퍼 함수의 속성을 유지하려면? ― functools.wraps

- @wraps(func) = “데코레이터를 써도 원래 함수의 이름과 설명은 그대로 두자!

구분 설명 예시 결과
기능 요약 래퍼(Wrapper) 함수가
원본 함수의 속성
(__name__, __doc__, 등)

을 그대로 유지하도록
해주는 데코레이터
@wraps(original_function) 래퍼 함수도 원본 함수처럼 보이게 함
문제 상황
(wraps 사용 전)
데코레이터를 쓰면
원래 함수 이름, 설명이
모두 래퍼 함수로 덮여버림
def outer(func):
       def wrapper():
               print("실행 전")
               func()
       return wrapper
@outer
def hello():
       """인사 함수"""
       print("안녕!")
print(hello.__name__)
print(hello.__doc__)
wrapper
None
해결 방법
(wraps 사용 후)
functools.wraps()로
원본 함수 정보를 복사
from functools import wraps
def outer(func):
       @wraps(func)
        def wrapper():
               print("실행 전")
               func()
       return wrapper
@outer
def hello():
       """인사 함수"""
       print("안녕!")
print(hello.__name__)
print(hello.__doc__)


hello
인사 함수
보존되는 속성 __name__,
__doc__,
__module__,
__annotations__ 등
예: hello.__name__ → "hello" 원본 함수 메타데이터 유지
필요 이유 디버깅, 로깅, 문서화(help()),
테스트 시 함수 이름/문서가
달라지면 혼란 발생
- wraps로 일관성 유지
위치 functools 모듈에 포함 from functools import wraps 표준 라이브러리 내장

라이브러리 예제 문제: add() 함수에 elapsed 데코레이터를 사용하더라도 함수 이름과 함수 설명문이 그대로 나오도록 하려면 어떻게 해야 할까?

점프 투 파이썬 - 라이브러리 예제 편 5장 33번 문제
점프 투 파이썬 - 라이브러리 예제 편 5장 33번 문제 해결 및 결과1
점프 투 파이썬 - 라이브러리 예제 편 5장 33번 문제 해결 및 결과2

034. 다양한 기준으로 정렬하려면? ― operator.itemgetter

구분 설명 예시 결과
기능 요약 객체(리스트, 튜플, 딕셔너리 등)에서 지정한 인덱스나 키의 값을 꺼내는 함수를 만들어 줌 from operator import itemgetter
get_second = itemgetter(1)
print(get_second(('A', 10)))
10
주 사용처 sorted()나 list.sort()에서
정렬 기준(key) 으로 사용
from operator import itemgetter
items = [('A', 2), ('C', 1), ('B', 3)]
print(sorted(items, key=itemgetter(1))
# 두 번째 값(1) 오름차순 정렬
[('C', 1), ('A', 2), ('B', 3)]
기본 문법 itemgetter(n) 또는
itemgetter(n1, n2, ...) 형태
from operator import itemgetter
data = ('x', 'y', 'z')
print(itemgetter(0, 2)(data)) #0번째, 2번째 값 추출
('x', 'z')
단일
기준 정렬
리스트의 한 인덱스를
기준으로 정렬
from operator import itemgetter
people = [('길동', 30), ('영희', 25), ('철수', 35)]
print(sorted(people, key=itemgetter(1)))
# 나이순(1) 오름차순 정렬
[('영희', 25), ('길동', 30), ('철수', 35)]
다중
기준 정렬
여러 인덱스를 기준으로 정렬할 수 있음 from operator import itemgetter
students = [('A', 2, 90), ('B', 1, 95), ('C', 1, 85)]
print(sorted(students, key=itemgetter(1, 2)))
# 두 번째 값(1) 오름차순 정렬
[('C', 1, 85), ('B', 1, 95), ('A', 2, 90)] 
딕셔너리
리스트 정렬
key 이름 기준으로 정렬 가능 from operator import itemgetter
users = [{'name': '철수', 'age': 30}, {'name': '영희', 'age': 25}]
print(sorted(users, key=itemgetter('age')))
# 나이순('age') 오름차순 정렬
[{'name': '영희', 'age': 25}, {'name': '철수', 'age': 30}]
lambda
비교
lambda x: x[n] 과 같은 역할이지만 더 빠름 from operator import itemgetter
data = [('A', 3), ('B', 1), ('C', 2)]
print(sorted(data, key=lambda x: x[1]))
print(sorted(data, key=itemgetter(1)))
# 학년(1) → 점수(2) 순 오름차순 정렬
[('B', 1), ('C', 2), ('A', 3)]
복수 키
정렬 결과
itemgetter(1, 2)은 (x[1], x[2]) 튜플 반환 → 자동 비교 from operator import itemgetter
records = [(1, 2, 3), (1, 1, 5), (1, 2, 1)]
print(sorted(records, key=itemgetter(1, 2)))
# 두 번째 값(1) → 세 번째 값(2) 순 오름차순 정렬
[(1, 1, 5), (1, 2, 1), (1, 2, 3)]
모듈 위치 operator 모듈에 포함된 함수 import operator
print(operator.itemgetter)
<class 'operator.itemgetter'>

라이브러리 예제 문제:

점프 투 파이썬 - 라이브러리 예제 편 5장 34번 문제
점프 투 파이썬 - 라이브러리 예제 편 5장 34번 문제 해설 및 결과