최근 프로젝트에서 cursor bug bot
이 생겼는데 공짜라서 유용하게 써보고 있습니다.
어떤 기능 개발을 한 뒤에 PR을 작성하게 되면 깃헙 액션을 통해서 알아서 어떤 버그를 발생시킬 수 있는지 문제가 없는지 확인을 해주는 건데요
아래와 같은 버그 리포팅을 버그봇으로 부터 받게 되었고, 그 문제를 어떻게 해결을 시켰었는지 한번 작성해보도록 하겠습니다.
이전 매니저의 매니저프리 신청건을 취소처리하는 비동기 함수기능이었습니다.
발생할 수 있는 버그는 바로 Mutable Default Argument 버그입니다.
🚨 예시 버그 상황
def add_item(item, my_list=[]):
my_list.append(item)
return my_list
# 첫 번째 호출
result1 = add_item("사과")
print(result1) # ['사과']
# 두 번째 호출
result2 = add_item("바나나")
print(result2) # ['사과', '바나나'] ?!
어? 뭔가 이상하지 않나요? 두 번째 호출에서는 ['바나나']
만 나와야 할 것 같은데, 첫 번째 호출의 '사과'
가 그대로 남아있습니다.
🔍 버그봇이 리포팅한 코드
@task
def async_before_manager_free_apply_cancel(
match_ids: list,
processed_apply_ids: list = [] # 🚨 여기가 문제!
) -> None:
# 이미 처리된 신청서는 제외하고 처리
for apply in applications.exclude(id__in=processed_apply_ids):
if cancel_success:
processed_apply_ids.append(apply.id)
# 실패한 경우 재시도
if failed_cases:
async_before_manager_free_apply_cancel.retry(
args=[failed_match_ids, processed_apply_ids]
)
이 코드의 문제점은 뭘까요? 첫 번째 함수 호출에서 processed_apply_ids
에 뭔가 추가되면, 다음 호출에서도 그 값들이 계속 남아있다는 겁니다. 결국 처리되어야 할 신청서들이 "이미 처리됐다"고 잘못 판단되어 건너뛰게 되죠.
🤔 왜 이런 일이 생길까?
Python에서 함수의 기본값은 함수가 정의될 때 단 한 번만 평가됩니다.
def show_time(timestamp=time.time()): # 함수 정의 시점에 한 번만 실행
return timestamp
print(show_time()) # 1640995200.123
time.sleep(2)
print(show_time()) # 1640995200.123 (같은 값!)
마찬가지로 my_list=[]
도 함수가 정의될 때 한 번만 생성되고, 모든 함수 호출에서 동일한 리스트 객체를 공유하게 됩니다.
✅ 해결 방법
간단합니다! 기본값으로 None
을 사용하고 함수 내부에서 새로운 객체를 생성하면 됩니다.
Before (문제가 있는 코드)
def add_item(item, my_list=[]):
my_list.append(item)
return my_list
After (수정된 코드)
def add_item(item, my_list=None):
if my_list is None:
my_list = []
my_list.append(item)
return my_list
🛠️ 실제 프로젝트 적용
저희 프로젝트에서는 이렇게 수정했습니다:
@task
def async_before_manager_free_apply_cancel(
match_ids: list,
processed_apply_ids: list | None = None # 타입 힌트도 수정
) -> None:
if processed_apply_ids is None:
processed_apply_ids = [] # 매번 새로운 리스트 생성
# 나머지 로직은 동일
for apply in applications.exclude(id__in=processed_apply_ids):
# ...
mypy를 사용하는 경우 타입 힌트도 list | None
또는 Optional[list]
로 수정해야 합니다.
🎯 추가 팁
1. 다른 mutable 타입들도 주의하세요
# 딕셔너리도 마찬가지!
def bad_function(data, cache={}): # ❌
pass
def good_function(data, cache=None): # ✅
if cache is None:
cache = {}
2. IDE나 린터 활용
- pylint:
dangerous-default-value
경고 - mypy: 타입 체크로 미리 발견 가능
- IDE 확장: 대부분의 Python IDE에서 경고 표시
3. 불변(immutable) 타입은 안전해요
def safe_function(value, default=0): # int는 안전
pass
def also_safe(text, prefix=""): # str도 안전
pass
🚀 마무리
Python의 mutable default argument는 처음에는 이해하기 어려울 수 있지만, 한 번 알고 나면 쉽게 피할 수 있는 함정입니다.
핵심은:
- 기본값으로
None
사용 - 함수 내부에서 새 객체 생성
- 타입 힌트 정확히 작성
여러분도 코드 리뷰할 때 def function(param=[])
같은 패턴을 발견하면 한 번 더 체크해보세요.
이런 실무 경험과 팁이 도움이 되셨나요? 댓글로 여러분의 Python 함정 경험도 공유해주세요!
'🌈 프로그래밍 > Python' 카테고리의 다른 글
[ python + github Action ] PR 리스트 자동화 하기 (with. pygithub) (0) | 2022.11.09 |
---|---|
[ python ] celery 모듈에 대해서 알아보자. (0) | 2022.09.22 |
[ Python ] 유용한 라이브러리 정리 (0) | 2022.04.06 |