파이썬58번5 MIN
파이썬 집합 컴프리헨션과 frozenset
파이썬 집합 컴프리헨션의 문법, 중복 제거 동작, 집합 연산(교집합·합집합·차집합), frozenset의 불변성과 해시 가능성을 실전 예제로 설명합니다.
이 글은 PALDYN Tech Blog의 글입니다.원문 보기
지난 글에서 딕셔너리 컴프리헨션을 살펴봤습니다. 이번에는 컴프리헨션 삼총사의 마지막인 집합 컴프리헨션(set comprehension) 과 불변 집합 frozenset 을 다룹니다.
집합 컴프리헨션 문법
{표현식 for 변수 in 이터러블 if 조건}
딕셔너리 컴프리헨션과 동일하게 {} 를 쓰지만, 키: 값 구분자가 없습니다. 결과는 set 타입으로 중복이 자동으로 제거되고, 순서가 보장되지 않습니다.
data = [1, 2, 2, 3, 3, 3, 4]
unique_squares = {x**2 for x in data}
# {1, 4, 9, 16} — 중복 제거, 순서 없음
빈 집합 주의
empty_set = set() # ✓ 빈 집합
empty_dict = {} # ← 빈 딕셔너리! 집합이 아님
type(set()) # <class 'set'>
type({}) # <class 'dict'>
집합 컴프리헨션은 {x for x in []} 으로 빈 집합을 만들 수 있지만, 리터럴로는 set() 을 써야 합니다.
중복 제거 패턴
리스트에서 고유 값만 추출할 때 집합 컴프리헨션이나 set() 변환을 씁니다.
# 방법 1 — 집합 컴프리헨션 (변환 로직 추가 가능)
words = ["Hello", "world", "hello", "WORLD"]
unique_lower = {w.lower() for w in words}
# {'hello', 'world'}
# 방법 2 — set() 직접 변환 (변환 없이 단순 중복 제거)
nums = [1, 2, 2, 3, 3, 3]
unique = set(nums)
# {1, 2, 3}
# 순서를 유지하면서 중복 제거 (Python 3.7+, dict는 삽입 순서 보장)
nums = [3, 1, 2, 1, 3, 2]
unique_ordered = list(dict.fromkeys(nums))
# [3, 1, 2]
실전: 고유 도메인 추출
emails = [
"alice@example.com",
"bob@gmail.com",
"charlie@example.com",
"diana@yahoo.com",
"eve@gmail.com",
]
domains = {email.split("@")[1] for email in emails}
# {'example.com', 'gmail.com', 'yahoo.com'}
# 특정 도메인 필터
internal = {e.split("@")[1] for e in emails if e.endswith("@example.com")}
# {'example.com'}
집합 연산과 컴프리헨션 조합
# 두 문서에서 공통 단어 찾기
doc1 = "the quick brown fox"
doc2 = "the slow brown dog"
words1 = {w.lower() for w in doc1.split()}
words2 = {w.lower() for w in doc2.split()}
common = words1 & words2 # {'the', 'brown'}
only_in_doc1 = words1 - words2 # {'quick', 'fox'}
all_words = words1 | words2 # {'the', 'quick', 'brown', 'fox', 'slow', 'dog'}
exclusive = words1 ^ words2 # {'quick', 'fox', 'slow', 'dog'}
집합 연산은 리스트 검색(in) 대비 훨씬 빠른 O(1) 멤버십 체크를 제공합니다.
멤버십 체크 성능
import timeit
large_list = list(range(100_000))
large_set = set(large_list)
# 리스트: O(n)
timeit.timeit(lambda: 99_999 in large_list, number=1000)
# 집합: O(1)
timeit.timeit(lambda: 99_999 in large_set, number=1000)
# 집합이 수십~수백 배 빠름
자주 멤버십 체크를 해야 하는 데이터는 집합으로 변환하는 것이 성능상 유리합니다.
frozenset — 불변 집합
frozenset 은 set 과 동일하지만 변경이 불가능합니다. 해시 가능(hashable)하므로 딕셔너리 키나 다른 집합의 원소로 사용할 수 있습니다.
# frozenset 생성
fs = frozenset([1, 2, 3])
fs2 = frozenset({4, 5, 6})
# 집합 연산 지원
fs | fs2 # frozenset({1, 2, 3, 4, 5, 6})
fs & fs2 # frozenset()
# 딕셔너리 키로 사용
cache = {}
key = frozenset({"a", "b", "c"})
cache[key] = "group-abc"
# set은 unhashable이라 키로 사용 불가
# {{"a", "b"}: "value"} → TypeError
frozenset 활용 — 그룹 캐싱
from functools import lru_cache
@lru_cache(maxsize=None)
def process_group(items: frozenset) -> int:
return sum(items)
# frozenset을 인자로 넘겨야 캐시 키로 사용 가능
process_group(frozenset([1, 2, 3])) # 6
process_group(frozenset([1, 2, 3])) # 캐시 히트
집합 컴프리헨션 vs set(리스트 컴프리헨션)
nums = range(10)
# 동등하지만 집합 컴프리헨션이 더 효율적
{x**2 for x in nums} # ✓ 집합 컴프리헨션
set([x**2 for x in nums]) # 리스트 먼저 만들고 집합으로 변환 — 메모리 낭비
set(x**2 for x in nums) # 제너레이터 표현식 + set() — 메모리 효율적, 동등
핵심 정리
{표현식 for ... in ... if ...}— set 반환, 중복 자동 제거, 순서 없음- 빈 집합은
set()—{}는 빈 딕셔너리 - 고유 값 추출·중복 제거·집합 연산에 최적
- 멤버십 체크는 집합이 O(1) — 리스트는 O(n)
frozenset은 불변·해시 가능 — 딕셔너리 키,lru_cache인자로 활용
지난 글: 파이썬 딕셔너리 컴프리헨션
다음 글: 파이썬 제너레이터 표현식과 지연 평가
읽어주셔서 감사합니다. 😊

