좋아, 정말 좋은 질문이야.
이번엔 파이썬 집합(set) 관련 고급 개념 중 하나인 **frozenset(고정 집합)**을 낱낱이 파헤쳐보자!
frozenset란?
frozenset은 **변경할 수 없는(immutable) 집합(set)**이야.
set: 값을 추가하거나 삭제할 수 있음 (mutable)frozenset: 절대로 값 추가, 삭제, 수정이 안 됨! (immutable)s = set([1, 2, 3]) # 일반 집합
fs = frozenset([1, 2, 3]) # 고정 집합
s.add(4) # OK
fs.add(4) # ❌ AttributeError!
frozenset은 아래 같은 이유로 필요해:set이나 dict의 key로 사용하려면
→ key는 반드시 immutable이어야 하니까
fs = frozenset([1, 2])
d = {fs: "ok"} # ✅ 가능
안정된 비교/캐싱이 필요할 때
→ 예: "이 조합이 예전에 나왔었는가?"를 기억해야 할 때
해시 가능
set은 hashable하지 않음 → 딕셔너리의 key로 못 씀frozenset은 hashable → key로 사용 가능| 항목 | set | frozenset |
|---|---|---|
| 변경 가능 여부 | ✅ 가능 | ❌ 불가능 (immutable) |
| 메서드 지원 | .add(), .remove() 등 있음 | 없음 (읽기 전용 메서드만 있음) |
| hashable 여부 | ❌ (딕셔너리 key 불가) | ✅ (딕셔너리 key 가능) |
| 사용 목적 | 일반적인 데이터 저장 | 고정된 값으로서의 안전한 비교 |
# 일반 set은 key로 못 씀!
# d = {set([1, 2]): "value"} # ❌ TypeError
# frozenset은 가능!
d = {frozenset([1, 2]): "value"}
print(d[frozenset([1, 2])]) # ✅ "value"
fs1 = frozenset([1, 2, 3])
fs2 = frozenset([2, 3, 4])
print(fs1.union(fs2)) # frozenset({1, 2, 3, 4})
print(fs1.intersection(fs2)) # frozenset({2, 3})
단, .add(), .remove() 같은 건 불가능!
a = frozenset([1, 2, 3])
b = set([a, frozenset([3, 4])])
print(b)
✅ 출력 가능 → set 안에 frozenset을 원소로 넣음
# {frozenset({1, 2, 3}), frozenset({3, 4})}
하지만 반대로 set([set([1, 2])]) 이런 건 ❌ 에러임
| 구분 | set | frozenset |
|---|---|---|
| 변경 가능 | ✅ 가능 | ❌ 불가능 (읽기 전용) |
| 메서드 | add/remove/update 가능 | union/intersection만 가능 |
| 해시 가능 | ❌ 아님 | ✅ 가능 |
| dict key 사용 | ❌ 안 됨 | ✅ 됨 |
| 리스트/딕셔너리 포함 여부 | ❌ 포함 못 함 | ❌ 포함 못 함 (hashable만 가능) |
정말 핵심을 찌르는 질문이야!
**"해시(hash)"**와 **"딕셔너리(dictionary)"**는 파이썬 뿐만 아니라 모든 프로그래밍에서 매우 중요한 기초이자 실전 개념이야.
즉, 딕셔너리의 핵심 동작 원리는 바로 **"해시"**야!
key와 value를 짝지어서 저장하는 자료구조
student = {
"name": "Hansun",
"age": 20,
"score": 95
}
"name"이라는 key는 "Hansun"이라는 value를 가진다key를 단순히 문자열로 저장하는 게 아니라,
key에 대해 "해시(hash)값"을 계산해서 위치를 빠르게 찾는 구조
데이터를 짧은 고정 길이 숫자로 바꾸는 알고리즘
hash("apple") → 1453365729483290 # 예시 숫자
hash() 함수는 문자열이든 숫자든 → 정수로 바꿔준다→ 검색할 때도 같은 해시값만 찾으면 되니까 빠름! (O(1) 시간)
"apple" → 해시 계산 → 5번 서랍 → 그 위치에 값 저장!
| 자료형 | 해시 가능? | 이유 |
|---|---|---|
int, float, str, tuple | ✅ 가능 | 값이 변경되지 않음 (immutable) |
list, dict, set | ❌ 불가능 | 값이 바뀔 수 있음 (mutable) |
frozenset | ✅ 가능 | 변경 불가능한 집합 |
d = {}
d[[1, 2]] = "안됨!" # ❌ 리스트는 해시 불가 → TypeError
d[(1, 2)] = "가능!" # ✅ 튜플은 해시 가능 → OK
d = {"name": "홍길동"}
→ "name" → hash("name") → 해시 버킷 위치 7번 → "홍길동" 저장
→ 다음에 d["name"] 하면, hash값으로 바로 위치 찾아감 → 빠름
| 개념 | 설명 |
|---|---|
| 딕셔너리 | Key-Value로 저장하는 파이썬의 고급 자료형 |
| 해시 | Key를 숫자로 바꾸는 계산 방식 (위치 빠르게 찾기 위함) |
| hashable | 해시 계산이 가능한 자료형 (변경 불가능한 것만 가능) |
| frozenset | 해시 가능한 집합 (그래서 dict의 key로 사용 가능) |
s = set()
fs = frozenset([1, 2])
d = {}
d[fs] = "ok"
print(d[fs])
출력 결과는?
✅ ok
(왜냐하면 frozenset은 해시 가능하니까 key로 사용 가능!)
정확히 짚었어!
파이썬의 핵심 개념 중 하나인 immutable(불변)과 mutable(가변)은
**값이 변경 가능한가? 아니면 한 번 정해지면 절대 바뀌지 않는가?**에 대한 이야기야.
| 구분 | 의미 | 예시 |
|---|---|---|
| immutable | 한 번 만들어진 값은 바뀔 수 없음 | int, float, str, tuple, frozenset |
| mutable | 값을 바꾸거나 수정할 수 있음 | list, dict, set, bytearray |
a = 10
b = a
a = a + 1
print(a) # 11
print(b) # 10 (b는 그대로!)
int는 immutable이기 때문에 a = a + 1을 하면 새로운 값이 만들어짐b는 영향을 안 받음lst1 = [1, 2, 3]
lst2 = lst1
lst1.append(4)
print(lst1) # [1, 2, 3, 4]
print(lst2) # [1, 2, 3, 4] (같이 바뀜!)
list는 mutable이기 때문에 lst1을 바꾸면 lst2도 같이 바뀜x = 5
print(id(x)) # 예: 123456
x += 1
print(id(x)) # 예: 789012 (다른 주소!)
l = [1, 2]
print(id(l)) # 예: 345678
l.append(3)
print(id(l)) # 여전히 345678
def add_item(lst):
lst.append(100)
my_list = [1, 2, 3]
add_item(my_list)
print(my_list) # ❗ [1, 2, 3, 100] - 원본도 바뀜
→ 리스트는 mutable이기 때문에 함수 안에서 바꾸면 원본도 바뀜
d = {}
d[[1, 2]] = "x" # ❌ 오류 (list는 mutable)
d[(1, 2)] = "y" # ✅ 튜플은 immutable
| 비유 | 예시 자료형 | 설명 |
|---|---|---|
| immutable = 돌에 새긴 글씨 | 숫자, 문자열, 튜플, frozenset | 한번 정하면 절대 못 바꿈 |
| mutable = 화이트보드 | 리스트, 딕셔너리, 집합 | 언제든 수정/삭제 가능 |
| 자료형 | 변경 가능? | hashable? | 딕셔너리 key로 가능? |
|---|---|---|---|
int | ❌ 불변 | ✅ | ✅ 가능 |
float | ❌ | ✅ | ✅ |
str | ❌ | ✅ | ✅ |
tuple | ❌ | ✅ (단, 내부도 immutable이어야) | ✅ |
list | ✅ 가변 | ❌ | ❌ 불가 |
dict | ✅ | ❌ | ❌ |
set | ✅ | ❌ | ❌ |
frozenset | ❌ | ✅ | ✅ |
✅ "바꾸면 안 되는 값"이면 immutable
✅ "바꿀 수 있는 값"이면 mutable
맞아!
**자바(Java)**나 C언어에도 파이썬에서 말하는 mutable과 immutable처럼
값을 "직접 바꿀 수 있느냐, 없느냐", 그리고 **값이 "복사되느냐, 참조되느냐"**에 따라
비슷한 개념이 존재해.
아래에 언어별로 비교 정리해줄게 — 차이점과 핵심도 같이!
| 특징 | 설명 |
|---|---|
| mutable | 값 자체를 바꿀 수 있음 → list, dict, set 등 |
| immutable | 값 자체를 못 바꿈 → int, float, str, tuple, frozenset |
자바는 파이썬처럼 명시적인 mutable/immutable 구분은 없지만,
"값형 vs 참조형" + final 키워드 + String의 불변성으로 비슷한 개념을 표현해.
| 자료형 | 불변성 있음? | 설명 |
|---|---|---|
int, double, char, boolean (primitive types) | ✅ 값 복사됨 | 값 자체가 복사됨 (stack에 존재) |
String | ✅ 불변 | 내부에서 값 못 바꿈. + 연산은 새 객체 생성 |
Integer, Double 등 Wrapper class | ✅ (불변처럼 취급) | 내부적으로 값 못 바꿈 (boxed value) |
🔸 예시:
String a = "hello";
String b = a;
a = "world";
System.out.println(b); // 출력: hello (a가 바뀌어도 b는 그대로)
→ String은 immutable이기 때문에 a가 바뀌면 새로운 객체가 만들어짐
| 자료형 | 가변성 있음? | 설명 |
|---|---|---|
ArrayList, HashMap, StringBuilder | ✅ 값 변경 가능 | 참조로 전달됨 |
🔸 예시:
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.set(0, 100);
System.out.println(list); // [100, 2]
→ 값이 직접 수정 가능 → mutable!
final 키워드와 혼동 금지!final ArrayList<Integer> list = new ArrayList<>();
list.add(1); // 가능!
list = new ArrayList<>(); // ❌ 에러!
final은 참조 자체를 못 바꾸는 것이지, 내용(mutable)은 바꿀 수 있어!C언어는 파이썬처럼 동적인 자료형은 없지만,
값을 직접 복사하느냐, 포인터를 사용하느냐로 비슷한 구조를 만들어.
int a = 10;
int b = a;
a = 20;
printf("%d\n", b); // 10
→ a의 값을 바꿔도 b는 그대로 → 값 복사 (stack 메모리)
void change(int* x) {
*x = 100;
}
int main() {
int a = 5;
change(&a);
printf("%d\n", a); // 100
}
→ 포인터를 통해 원본 값을 수정 가능 → mutable처럼 동작
const int x = 10;
x = 20; // ❌ 컴파일 에러
→ const는 파이썬의 immutable 같은 효과!
| 언어 | 불변 값 예시 | 가변 값 예시 | 비고 |
|---|---|---|---|
| Python | int, str, tuple | list, dict, set | .copy()로 복사 필요 |
| Java | String, Integer | ArrayList, HashMap | final은 참조만 고정 |
| C | int, const int | 포인터를 통한 직접 수정 | 메모리 주소 기반 처리 |
String), final 키워드로 조절