3-19 함수가 여러 값을 반환하는 경우 절대로 네 값 이상을 언패킹하지 마라
- 함수에서 여러 개의 값을 반환하는 방법
- 튜플을 사용하여 여러 값을 반환하고, 호출하는 쪽에서 언패킹하여 사용할 수 있다.
def get_stats(numbers):
minimum = min(numbers)
maximum = max(numbers)
return minimum, maximum
length = [10, 20, 30, 40, 50]
minimum, maximum = get_stats(length)
print(f'min: {minimum}, max: {maximum}')
# 출력: min: 10, max: 50
return minimum, maximum
에서 튜플을 반환하며, 이를 언패킹하여minimum, maximum
변수에 저장한다.
언패킹을 활용한 함수 반환
- 두 개의 값 반환 예제
def my_function():
return 1, 2
first, second = my_function()
assert first == 1
assert second == 2
- 함수가 두 개의 값을 반환하면 언패킹을 통해 변수에 쉽게 할당할 수 있다.
- 여러 개의 값을 반환하면서 일부를 리스트로 처리
def get_avg_ratio(numbers):
average = sum(numbers) / len(numbers)
scaled = [x / average for x in numbers]
scaled.sort(reverse=True)
return scaled
longest, *middle, shortest = get_avg_ratio(length)
print(f'최대 길이: {longest:>4.0%}')
print(f'최소 길이: {shortest:>4.0%}')
middle
을 사용하여 중간 값을 리스트로 묶어서 관리할 수 있다.
네 개 이상의 변수를 언패킹하지 말아야 하는 이유
- 변수 개수가 많아지면 실수할 가능성이 커진다.
- 언패킹할 변수가 많으면 가독성이 떨어지고, 실수하기 쉽다.
a, b, c, d, e = some_function() # 변수 개수가 많아 관리하기 어려움
- 대안:
namedtuple
이나 작은 클래스 반환
namedtuple
을 사용한 방법
from collections import namedtuple
Stats = namedtuple('Stats', ['min', 'max'])
def get_stats(numbers):
return Stats(min(numbers), max(numbers))
stats = get_stats(length)
print(stats.min, stats.max)
# 출력: 10 50
namedtuple
을 사용하면 반환 값이 의미를 가지므로 가독성이 향상된다.
작은 클래스를 사용하는 방법
class Stats:
def __init__(self, minimum, maximum):
self.minimum = minimum
self.maximum = maximum
def get_stats(numbers):
return Stats(min(numbers), max(numbers))
stats = get_stats(length)
print(stats.minimum, stats.maximum)
# 출력: 10 50
- 작은 클래스를 사용하면 추가적인 메서드를 정의할 수도 있고, 더 확장 가능하다.
기억해야 할 내용
- 여러 값을 반환할 때는 튜플을 사용할 수 있으며, 언패킹을 통해 개별 변수에 할당할 수 있다.
- 반환 값이 많을 경우 `` 연산자를 사용하여 일부 값을 리스트로 받을 수도 있다.
- 네 개 이상의 변수를 언패킹하는 것은 실수하기 쉬우므로 피해야 한다.
- 대신
namedtuple
이나 작은 클래스를 사용하여 반환하는 것이 더 나은 방법이다.
3-20 None
을 반환하기보다는 예외를 발생시켜라
None
을 반환하면,False
와 동등하게 평가되므로 코드에서 의도치 않은 오류를 일으킬 수 있다.특히,
None
이 특별한 의미를 가지는 경우,0
, 빈 문자열(""
),False
같은 값과 혼동될 위험이 있다.
None
을 반환하는 방식의 문제점def careful_divide(a, b): try: return a / b except ZeroDivisionError: return None # 잘못된 경우 None 반환 x, y = 0, 5 result = careful_divide(x, y) if not result: # result가 0이면 False로 평가됨 → 버그 발생 가능 print("잘못된 입력") # 의도치 않게 실행될 수 있음
result = 0
일 경우if not result:
가True
로 평가되어 잘못된 입력으로 간주하는 문제가 발생할 수 있다.
해결 방법 1: 튜플을 사용하여 명확한 반환 구조 만들기
def careful_divide(a, b): try: return True, a / b except ZeroDivisionError: return False, None # 오류 여부를 명확히 전달 success, result = careful_divide(10, 0) if success: print(f"결과: {result}") else: print("잘못된 입력")
- 튜플(
(성공 여부, 결과 값)
)을 반환하면 오류 여부를 명확히 전달할 수 있다. - 하지만, 함수 호출자가 튜플을 항상 언패킹해서 처리해야 하는 번거로움이 있다.
해결 방법 2: 예외를 발생시키기 (
추천
)def careful_divide(a, b): if b == 0: raise ValueError("잘못된 입력: 0으로 나눌 수 없음") return a / b try: result = careful_divide(10, 0) except ValueError as e: print(f"오류 발생: {e}")
- 예외(
ValueError
)를 발생시키면, 오류가 발생한 곳에서 바로 감지하고 처리할 수 있다. - 호출하는 쪽에서
try-except
를 통해 오류를 적절히 다룰 수 있다. - 불필요한
None
반환을 제거하여 코드의 명확성을 높인다.
타입 애너테이션을 사용한
None
반환 방지def careful_divide(a: float, b: float) -> float: if b == 0: raise ValueError("잘못된 입력: 0으로 나눌 수 없음") return a / b
> float
명시로None
이 반환되지 않음을 보장할 수 있다.- 정적 분석 도구(
mypy
)를 활용하면None
반환을 방지하는 데 도움을 받을 수 있다.
기억해야 할 내용
None
을 반환하면False
와 혼동될 수 있어 의도치 않은 오류를 유발할 수 있다.- 특별한 상황을 표현할 때는
None
을 반환하는 대신 예외(Exception
)를 발생시켜야 한다. - 예외를 발생시키면 함수 호출자가 오류를 명확하게 처리할 수 있다.
- 타입 애너테이션을 사용하면 함수가
None
을 반환하지 않는다는 점을 명확히 할 수 있다.
3-21 변수 영역과 클로저의 상호작용 방식을 이해하라
특정 숫자 그룹을 우선 배치하는 정렬 함수 구현
클로저(Closure)를 활용하여 정렬 기준을 설정
변수 영역(Scope) 문제 해결하기
1. 클로저를 사용한 정렬 예제
def sort_priority(values, group): def helper(x): # 정렬 기준을 정하는 내부 함수 if x in group: return (0, x) # 우선순위 그룹에 속하면 (0, x) return (1, x) # 그렇지 않으면 (1, x) values.sort(key=helper)
numbers = [8, 3, 1, 2, 5, 4, 7, 6] group = {2, 3, 5, 7} sort_priority(numbers, group) print(numbers) # 출력: [2, 3, 5, 7, 1, 4, 6, 8] (우선순위 그룹이 앞쪽으로 정렬됨)
- 이 코드가 정상 작동하는 이유
- 파이썬의 클로저 지원: 내부 함수
helper()
가sort_priority()
의group
에 접근 가능. - 일급 함수(First-Class Function): 파이썬에서는 함수를 변수에 저장하고, 인자로 전달 가능.
- 튜플 정렬 규칙:
(0, x)
와(1, x)
를 반환하면,0
이 앞에 배치되고x
기준으로 정렬됨.
- 파이썬의 클로저 지원: 내부 함수
2. 클로저 내부 변수 문제
우선순위 숫자가 존재하는지 확인하는
found
변수를 추가def sort_priority2(numbers, group): found = False # 우선순위 숫자가 존재하는지 확인하는 변수 def helper(x): if x in group: found = True # 우선순위 숫자를 찾으면 True로 설정 return (0, x) return (1, x) numbers.sort(key=helper) return found found = sort_priority2(numbers, group) print('발견:', found) # 기대값: True print(numbers) # 결과 발견: False (틀린 값) [2, 3, 5, 7, 1, 4, 6, 8] (정렬은 정상적으로 수행됨)
문제점:
found = True
가 실행되었음에도found
값이False
로 유지됨.이유:
helper()
함수 내부에서found = True
를 할당하면, 파이썬은found
를 새로운 지역 변수로 간주한다.- 따라서 외부의
found
변수에는 영향을 주지 않음.
- 따라서 외부의
3.
nonlocal
을 사용하여 변수 수정def sort_priority3(numbers, group): found = False # 우선순위 숫자 존재 여부 def helper(x): nonlocal found # 외부 함수의 found 변수를 수정하도록 명시 if x in group: found = True return (0, x) return (1, x) numbers.sort(key=helper) return found found = sort_priority3(numbers, group) print('발견:', found) # 올바르게 True 출력됨 print(numbers)
결과:
발견: True (올바른 값) [2, 3, 5, 7, 1, 4, 6, 8] (정렬도 정상 작동)
nonlocal found
를 추가하면 내부 함수에서 감싸는 함수의found
변수를 수정할 수 있다.
4. 변수 영역(Scope)의 동작 원리
현재 함수 내부에서 변수를 찾는다.
현재 함수를 감싸는 함수의 변수 영역을 찾는다.
현재 코드가 포함된 모듈(전역 변수)에서 찾는다.
파이썬 내장 영역에서 찾는다.
nonlocal
을 사용하면 지역 변수(helper()
내부)가 아닌 외부 함수(sort_priority3()
)의 변수를 수정할 수 있다.
기억해야 할 내용
- 클로저 함수는 자신이 정의된 함수의 변수도 참조할 수 있다.
- 클로저 내부에서 변수를 할당하면 새로운 지역 변수가 생성되므로, 감싸는 함수의 변수를 수정하려면
nonlocal
을 사용해야 한다. - 하지만,
nonlocal
을 사용하면 코드가 복잡해질 수 있으므로 간단한 함수가 아닐 경우 사용을 지양해야 한다.
- 이 코드가 정상 작동하는 이유
3-22 변수 위치 인자를 사용해 시각적인 잡음을 줄여라
가변 위치 인자(
*args
)를 사용하면 함수 호출이 더 깔끔해지고 시각적 잡음이 줄어든다.일반적인 방식 vs
*args
사용 방식 비교*args 예시
# 일반적인 함수 def add(a, b): return a + b # *args를 사용한 함수 def add_many(*args): return sum(args) # 사용 예시 print(add(1, 2,) # 3 , 만약에 add(1, 2, 3)하면 오류남 print(add_many(1, 2)) # 3 print(add_many(1, 2, 3, 4, 5)) # 15
1. 고정된 인자 개수를 사용하는 방식
def log(message, values):
if not values:
print(message)
else:
values_str = ', '.join(str(x) for x in values)
print(f'{message}: {values_str}')
log('내 숫자는', [1, 2])
log('안녕', [])
출력:
내 숫자는: 1, 2
안녕:
values
가 리스트이므로 빈 리스트라도 항상 전달해야 함 → 불필요한 시각적 잡음 발생
2. *args
를 사용한 가변 인자 방식 (추천)
def log(message, *values): # values가 가변 인자가 됨
if not values:
print(message)
else:
values_str = ', '.join(str(x) for x in values)
print(f'{message}: {values_str}')
log('내 숫자는', 1, 2) # 가변 인자로 숫자 전달
log('안녕') # 두 번째 인자 생략 가능
출력:
내 숫자는: 1, 2
안녕
*args
덕분에 필요한 인자만 입력하면 되고, 불필요한 리스트 감싸기를 없앨 수 있다.
3. 시퀀스를 *args
에 전달하는 방법 (``* 연산자 활용)
favorites = [7, 3, 9]
log('선호하는 숫자는', *favorites)
출력:
선호하는 숫자는: 7, 3, 9
favorites
를 사용하면 리스트의 원소가 개별 인자로 전달된다.- 리스트를 직접 넘기는 것보다 직관적이고 깔끔함.
4. *args
사용 시 주의할 점
(1) *args
는 항상 튜플로 변환됨
def example(*args):
print(args) # args는 튜플
example(1, 2, 3) # 출력: (1, 2, 3)
- 가변 인자는 자동으로 튜플로 변환되므로, 수정할 수 없다.
(2) 제너레이터에 *`` 연산자를 사용하면 메모리 초과 위험
def generator():
for i in range(10**6): # 100만 개 생성
yield i
# * 연산자 사용 시 주의
log('큰 숫자 목록', *generator()) # ❌ 메모리 초과 위험!
generator()
를 사용하면 모든 값이 튜플에 저장되므로 메모리 과부하 발생 가능- 해결 방법: ``를 사용하지 않고, 직접 순회하면서 처리해야 한다.
(3) *args
를 받는 함수에 새로운 위치 인자를 추가할 경우 주의
def func(*args):
print(args)
func(1, 2, 3) # 출력: (1, 2, 3)
def func_with_new_arg(x, *args):
print(x, args)
func_with_new_arg(1, 2, 3) # 출력: 1 (2, 3)
*args
앞에 새로운 위치 인자를 추가하면 기존 함수 호출 방식이 깨질 수 있음.- 기존 코드와 호환성을 유지하려면 신중하게 추가해야 함.
기억해야 할 내용
*args
를 사용하면 함수가 가변 위치 인자를 받을 수 있어 코드가 깔끔해진다.* 연산자를 사용하면 시퀀스의 원소를 개별 인자로 전달할 수 있다.
제너레이터에 ``* 연산자를 사용하면 메모리를 과도하게 사용할 위험이 있으므로 주의해야 한다.
*args
를 받는 함수에 새로운 위치 인자를 추가하면 예상치 못한 버그가 발생할 수 있다.인자(매개변수)
함수와 인자 간단 정리
1. 기본 인자 (Default Arguments)
def greet(name, message="안녕하세요"): # message는 기본값 있음 print(f"{message}, {name}님!") # 사용 예시 greet("철수") # "안녕하세요, 철수님!" greet("철수", "좋은 아침") # "좋은 아침, 철수님!"
2. 위치 인자 (Positional Arguments)
def introduce(name, age): print(f"이름: {name}, 나이: {age}") # 사용 예시 introduce("철수", 20) # 순서대로 들어감
3. 키워드 인자 (Keyword Arguments)
def introduce(name, age): print(f"이름: {name}, 나이: {age}") # 사용 예시 introduce(name="철수", age=20) # 이름 지정 introduce(age=20, name="철수") # 순서 바꿔도 됨
4. 가변 인자 (*args)
def print_names(*names): # 여러 개의 값을 받을 수 있음 for name in names: print(name) # 사용 예시 print_names("철수") # 하나만 전달 print_names("철수", "영희", "민수") # 여러 개 전달
5. 키워드 가변 인자 (kwargs)**
def print_info(**info): # 이름=값 형태로 여러 개 받음 for key, value in info.items(): print(f"{key}: {value}") # 사용 예시 print_info(name="철수", age=20, city="서울")
인자 사용 시 주의사항
- 위치 인자는 순서가 중요함
- 키워드 인자는 순서 상관없음
- 기본 인자는 선택적으로 사용 가능
- args는 여러 개의 위치 인자를 튜플로 받음
- *kwargs는 여러 개의 키워드 인자를 딕셔너리로 받음
실제 활용 예시
def user_profile(name, # 필수 위치 인자 age, # 필수 위치 인자 location="서울", # 기본 인자 *hobbies, # 가변 인자 **additional_info): # 키워드 가변 인자 print(f"이름: {name}") print(f"나이: {age}") print(f"위치: {location}") print("취미:", ", ".join(hobbies)) print("추가정보:") for key, value in additional_info.items(): print(f"- {key}: {value}") # 함수 호출 user_profile("철수", 20, "부산", "축구", "게임", "독서", 직업="학생", 이메일="chul@example.com")
3-23 키워드 인자로 선택적인 기능을 제공하라
기억해야 할 내용
- 함수 인자는 위치 인자 또는 키워드 인자로 지정할 수 있다.
- 키워드 인자를 사용하면 여러 인자의 목적을 명확히 할 수 있어 가독성이 향상된다.
- 키워드 인자와 디폴트 값을 함께 사용하면 기존 코드 변경 없이 새로운 기능을 쉽게 추가할 수 있다.
- 선택적 키워드 인자는 항상 키워드를 사용해 전달해야 한다.
예제 코드
# 딕셔너리 준비
user_info = {
"name": "철수",
"age": 20,
"city": "서울"
}
# 방법 1: 키워드 인자로 직접 전달
def introduce(name, age, city):
print(f"{name}는 {age}살이고 {city}에 삽니다")
introduce(age=20, name="철수", city="서울")
# 방법 2: ** 연산자로 딕셔너리 풀어서 전달
introduce(**user_info) # 위와 동일한 결과
# 결과
# 철수는 20살이고 서울에 삽니다
# 철수는 20살이고 서울에 삽니다
이 방법을 활용하면 기본값을 추가하여 새로운 기능을 쉽게 확장할 수도 있다.
def introduce(name, age, city="알 수 없음"):
print(f"{name}는 {age}살이고 {city}에 삽니다")
introduce(name="영희", age=25) # city 기본값 사용
# 결과
# 영희는 25살이고 알 수 없음에 삽니다
3-24 None
과 독스트링을 사용해 동적인 디폴트 인자를 지정하라
- 디폴트 인자는 함수 정의 시 한 번만 평가되므로, 동적인 값이 필요할 때 문제가 발생할 수 있다.
- 해결 방법:
None
을 기본값으로 설정하고, 함수 내에서 동적 값을 직접 할당하는 방식 사용
1. 잘못된 예제: 디폴트 인자로 datetime.now()
사용
from time import sleep
from datetime import datetime
def log(message, when=datetime.now()): # 잘못된 방법
print(f'{when}: {message}')
log('안녕')
sleep(0.1)
log('다시 안녕')
출력:
2025-01-31 12:00:00.000000: 안녕
2025-01-31 12:00:00.000000: 다시 안녕
- 문제점:
datetime.now()
가 함수 정의 시점에 한 번만 실행되므로, 이후 함수 호출에서도 변경되지 않는 동일한 값이 사용됨.
2. 올바른 예제: None
을 사용하여 동적 값 설정
def log(message, when=None):
"""메시지와 타임스탬프를 로그에 남긴다.
Args:
message: 출력할 메시지.
when: 메시지가 발생한 시각(datetime).
디폴트 값은 현재 시간이다.
"""
if when is None: # 호출 시점마다 새로운 시간 할당
when = datetime.now()
print(f'{when}: {message}')
log('안녕')
sleep(0.1)
log('다시 안녕')
출력:
2025-01-31 12:00:00.000000: 안녕
2025-01-31 12:00:00.100000: 다시 안녕
None
을 기본값으로 설정한 후, 함수 내부에서datetime.now()
를 호출하여 새로운 시간을 할당.- 이제 함수 호출 시마다 올바른 시간 값이 사용됨.
3. 타입 애너테이션을 활용한 예제
from typing import Optional
def log(message: str, when: Optional[datetime] = None) -> None:
"""메시지와 타임스탬프를 로그에 남긴다.
Args:
message (str): 출력할 메시지.
when (Optional[datetime]): 메시지가 발생한 시각.
기본값은 현재 시간(datetime.now())이다.
"""
if when is None:
when = datetime.now()
print(f'{when}: {message}')
Optional[datetime]
을 사용하여None
을 기본값으로 설정할 수 있음을 명시- 정적 분석 도구(
mypy
등)에서 더 정확한 검사를 수행할 수 있음
기억해야 할 내용
- 디폴트 인자는 함수 정의 시점에 한 번만 평가되므로,
datetime.now()
,{}
,[]
같은 동적인 값은 잘못된 동작을 유발할 수 있다. - 동적인 값을 디폴트 값으로 사용하려면
None
을 설정하고, 함수 내부에서 필요한 값을 할당해야 한다. - 독스트링에
None
이 사용되는 이유와 동작 방식에 대한 설명을 추가해야 한다. - 타입 애너테이션을 사용할 때도
Optional
을 활용해None
을 기본값으로 설정하는 방식을 적용할 수 있다.
3-25 위치로만 인자를 지정하게 하거나 키워드로만 인자를 지정하게 해서 함수 호출을 명확하게 만들라.
키워드 인자를 사용하면 함수 호출의 의도를 명확히 할 수 있다.
위치 전용 인자를 사용하면 함수 호출과 구현 간의 결합을 줄일 수 있다.
/
와*
기호를 사용하여 위치 전용 인자와 키워드 전용 인자를 지정할 수 있다.
1. 키워드 전용 인자 (
*
사용)def process_data(value, *, mode="default"): print(f"Processing {value} with mode: {mode}") # 올바른 호출 방식 process_data(10, mode="fast") # 잘못된 호출 방식 (mode를 키워드 없이 위치로 전달) process_data(10, "fast") # TypeError 발생
mode
인자는 `` 뒤에 위치하므로 반드시 키워드 인자로 전달해야 한다.- 함수 호출 시
mode="fast"
처럼 명시적으로 키워드를 사용해야 한다.
2. 위치 전용 인자 (
/
사용)def multiply(x, y, /): return x * y # 올바른 호출 방식 result = multiply(2, 3) # 잘못된 호출 방식 (키워드를 사용하면 오류 발생) result = multiply(x=2, y=3) # TypeError 발생
x
와y
는/
앞에 있으므로 위치 인자로만 전달 가능하다.- 키워드 인자로 전달하면
TypeError
가 발생한다.
3. 위치 또는 키워드 사용 가능 (
/
와*
사이)def configure(a, b, /, c, d, *, e, f): print(a, b, c, d, e, f) # 올바른 호출 방식 configure(1, 2, 3, d=4, e=5, f=6) # 위치 및 키워드 혼용 가능 # 잘못된 호출 방식 configure(a=1, b=2, c=3, d=4, e=5, f=6) # TypeError 발생
a, b
는 위치 전용 (/
앞)c, d
는 위치 또는 키워드 사용 가능 (/
와*
사이)e, f
는 키워드 전용 (*
뒤)
기억해야 할 내용
- 키워드 전용 인자 (
*
뒤에 위치) → 반드시 키워드로 지정해야 한다. - 위치 전용 인자 (
/
앞에 위치) → 반드시 위치로만 전달해야 한다. /
와*
사이의 인자는 위치 또는 키워드로 전달할 수 있다.- 명확한 함수 호출을 위해 적절히 활용해야 한다.
3-26 functools.wraps
을 사용해 함수 데코레이터를 정의하라.
데코레이터는 기존 함수를 수정하지 않고 추가 기능을 덧붙일 수 있는 문법이다.
데코레이터란?
def 데코레이터_함수(func): def wrapper(): # 함수 실행 전 작업 func() # 원래 함수 실행 # 함수 실행 후 작업 return wrapper @데코레이터_함수 def 일반_함수(): pass
실용적인 예제:
from functools import wraps import time def 시간측정(func): @wraps(func) def wrapper(): 시작 = time.time() func() 종료 = time.time() print(f"실행시간: {종료 - 시작:.2f}초") return wrapper @시간측정 def 테스트(): time.sleep(1) # 1초 대기 print("작업 완료") # 함수 실행 테스트() # 이 부분이 추가되었습니다
실행 결과:
작업 완료 실행시간: 1.00초
wraps
을 사용하면 원래 함수의 이름과 독스트링이 유지된다.wraps란?
wraps란?
파이썬의 데코레이터를 위한 데코레이터
원본 함수의 정보(이름, 설명문 등)를 보존하는 역할
실제 상황으로 비유
선물 포장 시 "내용물 설명서"를 붙이는 것과 비슷
wraps를 사용하지 않으면 포장지의 정보만 보이고, 안에 무엇이 들었는지 알 수 없음.
wraps를 사용하면 포장을 했어도 원래 선물의 정보를 그대로 볼 수 있음.
from functools import wraps # wraps 미사용 def 데코레이터1(func): def wrapper(): return func() return wrapper # wraps 사용 def 데코레이터2(func): @wraps(func) def wrapper(): return func() return wrapper @데코레이터1 def 함수1(): """첫 번째 함수입니다""" pass @데코레이터2 def 함수2(): """두 번째 함수입니다""" pass # 결과 확인 print(함수1.__name__) # 출력: wrapper print(함수2.__name__) # 출력: 함수2
즉, wraps는 데코레이터를 사용할 때 원본 함수의 정체성을 지켜주는 도우미.
데코레이터를 사용하면 디버거 등 인트로스펙션 도구가 잘못 작동할 수 있으므로
functools.wraps
를 활용해야 한다.
1.
wraps
없이 데코레이터를 사용한 경우import time def 시간측정(func): def wrapper(): 시작 = time.time() func() 종료 = time.time() print(f"실행시간: {종료 - 시작:.2f}초") return wrapper @시간측정 def 테스트(): """1초 동안 대기하는 테스트 함수""" time.sleep(1) print("작업 완료") # 함수 이름과 독스트링 확인 print(테스트.__name__) # wrapper (원래 함수명이 유지되지 않음) print(테스트.__doc__) # None (독스트링이 사라짐)
테스트.__name__
이wrapper
로 변경됨 → 원래 함수 이름이 유지되지 않음.테스트.__doc__
이None
이 됨 → 함수의 설명이 사라짐.
2.
wraps
을 사용한 경우from functools import wraps import time def 시간측정(func): @wraps(func) # 원래 함수의 메타데이터 유지 def wrapper(): 시작 = time.time() func() 종료 = time.time() print(f"실행시간: {종료 - 시작:.2f}초") return wrapper @시간측정 def 테스트(): """1초 동안 대기하는 테스트 함수""" time.sleep(1) print("작업 완료") # 함수 실행 테스트() # 함수 이름과 독스트링 확인 print(테스트.__name__) # 테스트 (원래 함수명이 유지됨) print(테스트.__doc__) # "1초 동안 대기하는 테스트 함수" (독스트링 유지됨)
출력 예시:
작업 완료 실행시간: 1.00초 테스트 1초 동안 대기하는 테스트 함수
wraps
을 사용하면 원래 함수 이름(테스트
)과 독스트링이 유지됨.
3. 매개변수가 있는 데코레이터 구현
def 반복(횟수): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for _ in range(횟수): func(*args, **kwargs) return wrapper return decorator @반복(3) def 인사(이름): """이름을 입력받아 인사를 출력하는 함수""" print(f"안녕하세요, {이름}님!") 인사("훈") # 3번 반복 출력 print(인사.__name__) # 인사 print(인사.__doc__) # "이름을 입력받아 인사를 출력하는 함수"
wraps
을 사용하지 않으면인사.__name__
이wrapper
로 바뀌고, 독스트링도 사라진다.wraps
을 사용하면 원래 함수의 메타데이터를 유지하면서 기능을 확장할 수 있다.
기억해야 할 내용
- 데코레이터는 함수 호출 전후로 추가 기능을 실행할 수 있다.
- 데코레이터를 사용할 경우 원래 함수의 메타데이터(
__name__
,__doc__
)가 변경될 수 있다. - 이를 방지하려면
functools.wraps
을 사용해야 한다. - 매개변수를 받는 데코레이터도
wraps
을 적용할 수 있다.
'리뷰 > 책' 카테고리의 다른 글
[파이썬 코딩의 기술] - 2장 리스트와 딕셔너리 (0) | 2025.02.14 |
---|---|
[파이썬 코딩의 기술] - 1장 파이썬답게 생각하기 (0) | 2025.02.14 |
Do it! 첫 코딩 - 5장 (0) | 2025.02.14 |
Do it! 첫 코딩 - 4장 (0) | 2025.02.14 |
Do it! 첫 코딩 - 3장 (0) | 2025.02.14 |