🌈 프로그래밍/TIL
Django ORM __in 조회에 딕셔너리 전달하면 어떻게 될까?
수구리
2025. 12. 18. 19:24
반응형
오늘 코드 리뷰 받다가 알게 된 내용 정리.
발단
프라임타임 점수 계산 로직을 짜면서 이렇게 썼었다:
SCORE_BY_HOUR = {
19: 1,
20: 2,
21: 1,
}
# 이렇게 썼음
FixedMatch.objects.filter(
match_time__hour__in=list(self.SCORE_BY_HOUR.keys()), # [19, 20, 21]
)
그런데 리뷰에서 list() 변환이 불필요하다는 피드백을 받았다.
# 이렇게 해도 된다고?
FixedMatch.objects.filter(
match_time__hour__in=self.SCORE_BY_HOUR, # dict 자체를 전달
)
왜 동작하는가?
Python 딕셔너리의 기본 동작
Python에서 딕셔너리를 iterate하면 기본적으로 keys를 순회한다.
d = {19: 1, 20: 2, 21: 1}
# 이 세 개는 동일한 결과
for key in d:
print(key) # 19, 20, 21
for key in d.keys():
print(key) # 19, 20, 21
list(d) # [19, 20, 21]
Django ORM의 __in 처리
Django의 __in lookup은 내부적으로 전달받은 값을 iterate해서 SQL의 IN 절로 변환한다.
# django/db/models/lookups.py 의 In 클래스 (간략화)
class In(FieldGetDbPrepValueIterableMixin, Lookup):
lookup_name = 'in'
def get_prep_lookup(self):
# self.rhs가 iterable이면 그대로 사용
return super().get_prep_lookup()
핵심은 FieldGetDbPrepValueIterableMixin인데, 이 믹스인이 하는 일:
# django/db/models/lookups.py (간략화)
class FieldGetDbPrepValueIterableMixin:
def resolve_expression_parameter(self, compiler, connection, sql, param):
# param이 iterable이면 각 요소를 처리
params = []
for p in param: # <- 여기서 iterate!
params.append(...)
return ...
for p in param 부분에서 딕셔너리가 전달되면 자연스럽게 keys를 순회하게 된다.
실제로 생성되는 SQL
# 두 쿼리 모두 동일한 SQL 생성
FixedMatch.objects.filter(match_time__hour__in=list(SCORE_BY_HOUR.keys()))
FixedMatch.objects.filter(match_time__hour__in=SCORE_BY_HOUR)
# SQL:
# SELECT * FROM fixed_match WHERE HOUR(match_time) IN (19, 20, 21)
결론
__in에 딕셔너리를 전달하면 자동으로 keys로 필터링됨list(dict.keys())변환은 불필요한 리스트 생성 오버헤드- 코드도 더 간결해짐
# Before
filter(field__in=list(my_dict.keys()))
# After
filter(field__in=my_dict)
주의사항
values로 필터링하고 싶으면 명시적으로 .values() 호출 필요:
# keys로 필터링 (기본)
filter(hour__in=SCORE_BY_HOUR) # hour IN (19, 20, 21)
# values로 필터링
filter(score__in=SCORE_BY_HOUR.values()) # score IN (1, 2, 1) -> (1, 2)
반응형