좋아! 지금부터 아까 낸 심화 복습 문제 1번부터 5번까지,
각 줄마다 무슨 역할을 하는지, 왜 그런 결과가 나오는지, 실제로 어떤 값이 처리되는지,
혼동 포인트와 실전 트랩까지 모두 포함해서 해부해줄게.
def modify_and_sort(lst):
lst2 = lst[:] # 얕은 복사
lst2.sort()
lst2[0][0] = 999
return lst, lst2
a = [[3], [1], [2]]
b, c = modify_and_sort(a)
print("a =", a)
print("sorted_copy =", c)
a = [[3], [1], [2]]
a → [[3], [1], [2]]lst2 = lst[:]
lst2[0] is lst[0] → Truelst2.sort()
lst2 → [[1], [2], [3]]lst2[0][0] = 999
lst2[0]은 [1] → 내부 값 1을 999로 바꿈lst[1]도 [1]을 참조 중이므로 lst[1][0] = 999가 됨| 변수 | 내용 |
|---|---|
lst (== a) | [[3], [999], [2]] |
lst2 (== c) | [[999], [2], [3]] |
a = [[3], [999], [2]]
sorted_copy = [[999], [2], [3]]
정답: 없음 (선택지 수정 필요)
→ 만약 보기 중에 이런 선택지가 있었다면:
✅ 정답: a = [[3], [999], [2]], sorted_copy = [[999], [2], [3]]
from collections import defaultdict
text = "apple banana apple grape banana apple"
word_count = defaultdict(int)
for word in text.split():
word_count[word] += 1
sorted_words = sorted(word_count.items(), key=lambda x: (-x[1], x[0]))
result = " ".join([f"{word}:{count}" for word, count in sorted_words])
print(result)
text.split() → ['apple', 'banana', 'apple', 'grape', 'banana', 'apple']
defaultdict(int) → 존재하지 않는 key에 접근하면 기본값 0
반복하면서 카운트:
'apple' → 1
'banana' → 1
'apple' → 2
'grape' → 1
'banana' → 2
'apple' → 3
sorted(..., key=lambda x: (-x[1], x[0]))
[('apple', 3), ('banana', 2), ('grape', 1)]" ".join(...) → 'apple:3 banana:2 grape:1'
D. 'apple:3 banana:2 grape:1'
*args + lambda + sort 조건def custom_sort(*args, reverse=False):
return sorted(args, key=lambda x: (len(x), x), reverse=reverse)
print(custom_sort("apple", "cat", "banana", "bee", reverse=True))
*args로 받은 값들: args → ("apple", "cat", "banana", "bee")
key=lambda x: (len(x), x)
reverse=True → 정렬 기준 반대 (긴 게 먼저)
정렬 기준 리스트:
| 문자열 | 길이 | 알파벳순 | 튜플 |
|---|---|---|---|
| apple | 5 | - | (5, 'apple') |
| cat | 3 | - | (3, 'cat') |
| banana | 6 | - | (6, 'banana') |
| bee | 3 | - | (3, 'bee') |
정렬:
reverse=True → 위 순서대로
A. ['banana', 'apple', 'cat', 'bee']
Counter + [::-1]from collections import Counter
s = "mississippi"
cnt = Counter(s)
items = list(cnt.items())[::-1]
print(items)
문자열: "mississippi"
카운트 결과:
| 문자 | 횟수 |
|---|---|
| m | 1 |
| i | 4 |
| s | 4 |
| p | 2 |
→ 사전 순서대로 저장 (dict는 3.7+부터 순서 유지함)
.items() → [('m', 1), ('i', 4), ('s', 4), ('p', 2)]
[::-1] → [('p', 2), ('s', 4), ('i', 4), ('m', 1)]
B. ('p', 2)
__init__ 기본값 트랩class Node:
def __init__(self, children=[]):
self.children = children
n1 = Node()
n2 = Node()
n1.children.append("leaf")
print(n2.children)
children=[] 는 한 번만 생성됨 (함수 정의 시)따라서 n1이 append("leaf") 하면
n2도 그 리스트를 보고 있기 때문에 영향을 받음
B. ['leaf']
| 문제 | 정답 | 핵심 트랩 |
|---|---|---|
| 1 | (직접 분석 결과) | 얕은 복사 + 내부 리스트 공유 |
| 2 | D | defaultdict, split + 정렬 key |
| 3 | A | 정렬 기준 튜플 + reverse |
| 4 | B | Counter + 역순 슬라이싱 |
| 5 | B | 기본값 리스트 공유 트랩 |
좋아, 문제 1번은 파이썬 리스트의 **얕은 복사(shallow copy)**와
내부 리스트 참조 공유, 그리고 정렬 후의 구조 변화를 이해해야 풀 수 있는 문제야.
우리가 이 문제를 **1줄씩 "실제로 무슨 일이 벌어지는가?"**를 따라가며
진짜로 메모리에 뭐가 저장되는지, 어떤 값이 바뀌는지를 디버깅처럼 해부해볼게.
def modify_and_sort(lst):
lst2 = lst[:] # 얕은 복사
lst2.sort() # 내부 리스트 정렬
lst2[0][0] = 999 # 내부 값 변경
return lst, lst2
a = [[3], [1], [2]]
b, c = modify_and_sort(a)
print("a =", a)
print("sorted_copy =", c)
a = [[3], [1], [2]]
메모리 구조는 이렇게 생겼어:
a --> [ [3], [1], [2] ]
↑ ↑ ↑
리스트1 리스트2 리스트3
id1 id2 id3
각 a[0], a[1], a[2]는 서로 **다른 객체(리스트)**이고, 각각 [3], [1], [2]를 담고 있어.
lst2 = lst[:]이 줄은 **얕은 복사(shallow copy)**야. 무슨 말이냐면:
lst2라는 새로운 리스트 객체를 만들되lst에 있던 원소들과 같은 객체(=참조 공유)**를 넣는 거야그래서 이렇게 됨:
lst2 --> [ [3], [1], [2] ]
↑ ↑ ↑
같은 객체들 (id1, id2, id3)
즉, lst2[0] is lst[0] → True
lst2.sort()이건 리스트 안의 요소를 정렬해.
[ [3], [1], [2] ][ [1], [2], [3] ]중요한 점:
정렬은 lst2의 바깥 리스트 순서만 바꾸지, 내부 객체는 그대로 유지돼.
즉, 구조는 이렇게 됨:
lst2 --> [ [1], [2], [3] ] ← 정렬된 순서
lst --> [ [3], [1], [2] ] ← 원래 순서
하지만 여전히 lst2[0] is lst[1] → True (참조는 공유된 상태)
lst2[0][0] = 999지금 lst2[0] == [1] 이니까, lst2[0][0] 은 1이야.
여기에 999를 넣으면 lst2[0] == [999]가 돼.
하지만!!!
lst2[0]은 lst[1]과 같은 객체였지?
그래서 이 순간 리스트 구조가 이렇게 변해버려:
lst → [[3], [999], [2]]
lst2 → [[999], [2], [3]]
내부 객체 하나를 바꿨더니, 원래 리스트도 같이 바뀌었어.
→ 이게 바로 얕은 복사의 핵심 트랩
| 변수 | 구조 | 설명 |
|---|---|---|
lst (a) | [[3], [999], [2]] | 내부 요소가 바뀜 |
lst2 (c) | [[999], [2], [3]] | 정렬된 후, 첫 원소 내부 값 변경됨 |
a = [[3], [999], [2]]
sorted_copy = [[999], [2], [3]]
| 개념 | 설명 | 자주 나오는 트랩 |
|---|---|---|
lst[:] | 얕은 복사 | 내부 객체는 여전히 공유됨 |
sort() | 원본 리스트 자체를 정렬 | 정렬 후 참조 순서 변경 가능 |
| 리스트 안의 리스트 | 내부 객체는 별개로 관리됨 | 얕은 복사 시 같이 바뀔 수 있음 |
import copy
lst2 = copy.deepcopy(lst)
→ 깊은 복사를 하면 내부 객체까지 새로 만들어지니까,
lst와 lst2는 완전히 독립적인 리스트가 돼서 이런 일이 안 생겨.
좋아, 그럼 지금부터 **sort()와 sorted()**를 완전히 확실하게 이해하도록
"한 줄도 빠짐없이", 예제와 함께,
언제 값이 변하고, 언제 None이 나오는지, 왜 그런 일이 발생하는지를 디버깅처럼 따라가며 설명해줄게.
| 항목 | sort() | sorted() |
|---|---|---|
| 원본 리스트 | 바뀐다 ✅ | 안 바뀐다 ❌ |
| 리턴값 | None ⛔ | 정렬된 새 리스트 ✅ |
| 사용 가능 대상 | 리스트만 가능 | 모든 반복 가능한 것 |
| 용도 | 정렬만 하고 저장은 안 함 | 정렬 결과를 리턴받아 저장 가능 |
sort() – 리스트 객체의 메서드 (원본을 정렬, 반환값 없음)a = [3, 1, 2]
result = a.sort()
print("a:", a)
print("result:", result)
a.sort()
None임 (아무것도 리턴하지 않음!)print("a:", a)
[1, 2, 3]a는 정렬되었기 때문print("result:", result)
Nonesort()는 값을 리턴하지 않음a = [3, 1, 2]
b = a.sort()
print(b[0]) # ❌ TypeError: 'NoneType' object is not subscriptable
왜 터지냐?
a.sort()는 None을 반환하니까b는 None이 되고b[0]은 None[0]이 되어 에러sorted() – 새로운 정렬된 리스트를 반환하는 함수a = [3, 1, 2]
b = sorted(a)
print("a:", a)
print("b:", b)
sorted(a)는 a를 복사해서 정렬한 새 리스트를 리턴a는 바뀌지 않음b는 [1, 2, 3]a = [3, 1, 2] # 원본
b = sorted(a) # 새로운 리스트 [1, 2, 3] 생성
a = [5, 2, 9]
b = a.sort()
print("a =", a)
print("b =", b)
출력:
a = [2, 5, 9]
b = None
a = [5, 2, 9]
b = sorted(a)
print("a =", a) # [5, 2, 9]
print("b =", b) # [2, 5, 9]
둘 다 다음 옵션 사용 가능:
sorted(list, key=..., reverse=...)
list.sort(key=..., reverse=...)
예:
a = ['ccc', 'a', 'bb']
a.sort(key=len)
print(a) # ['a', 'bb', 'ccc']
같은 결과:
a = ['ccc', 'a', 'bb']
b = sorted(a, key=len)
print(b) # ['a', 'bb', 'ccc']
sort()를 쓰고 언제 sorted()를 써야 할까?| 상황 | 선택 | 이유 |
|---|---|---|
| 결과를 새로 저장하고 싶다 | sorted() | 새 리스트 반환 |
| 리스트 자체를 바꿔도 괜찮다 | sort() | 메모리 효율 |
| 리스트가 아닌 것을 정렬 | sorted() | 문자열, 튜플 등도 가능 |
a = [5, 3, 2]
b = a.sort()
print(b) # ❌ None
→ b = sorted(a)로 바꿔야 원하는 결과가 저장됨
a = [[3], [1], [2]]
a.sort()
print(a) # [[1], [2], [3]]
[1] < [2] < [3])key=lambda x: x[0] 등을 써줘야 안전| 항목 | sort() | sorted() |
|---|---|---|
| 원본 변경 | ✅ 함 | ❌ 안 함 |
| 리턴값 | None | 정렬된 리스트 |
| 메모리 효율 | 좋음 | 새 리스트 만듦 |
| 사용 대상 | 리스트 전용 | 모든 반복형 |
| 실수 위험 | 높음 (리턴 없음) | 낮음 |
좋아! 아주 좋은 질문이야. 지금 헷갈리는 포인트는 정말 많은 사람들이 실수하는 핵심 트랩이기도 해.
너가 말한 이 부분:
lst2.sort() # 이거 때문에 None 나와야 하는 거 아니야?
이 문장 때문에 None이 나올 것처럼 헷갈린 이유를 완전히 정확하게 짚어서 설명해줄게.
lst2.sort()
lst2.sort() 자체는 None을 반환하지만,lst2 리스트는 정상적으로 정렬되고, 값도 그대로 유지됨None이 안 나오냐면:None이 들어감:lst2 = lst2.sort() # ← 이러면 lst2가 None이 됨 ❌
이건 정말 잘못된 코드야. 이렇게 쓰면 lst2가 아예 None으로 바뀌기 때문에 이후에 에러 발생함.
lst2 = lst[:] # 얕은 복사
lst2.sort() # 정렬하되, 반환값은 무시 (아무 변수에도 저장 안 함)
lst2[0][0] = 999 # 내부 값을 변경
lst2.sort()는 단지 lst2 리스트 자체를 정렬만 하고,lst2는 여전히 리스트다.a = [3, 1, 2]
a.sort()
print(a) # ✅ [1, 2, 3]
a.sort()는 정렬만 함a는 여전히 리스트a = [3, 1, 2]
a = a.sort() # ❌ 여기서 a가 None으로 바뀜
print(a) # None
a.sort()는 None을 리턴함a = 로 받아서 a가 None이 되어버림def modify_and_sort(lst):
lst2 = lst[:] # 얕은 복사 → lst2는 리스트
lst2.sort() # 정렬만 하고 리턴값은 안 씀 → lst2 여전히 리스트
lst2[0][0] = 999 # 내부 요소 직접 변경
return lst, lst2 # 둘 다 리스트 상태
✔ 어디에서도 .sort()의 리턴값을 변수에 저장하지 않았기 때문에, lst2는 None이 되지 않아!
| 코드 | 설명 |
|---|---|
lst2 = lst[:] | 얕은 복사 (리스트 복사) |
lst2.sort() | 정렬, 리턴값 무시 |
lst2 = lst2.sort() | ❌ lst2가 None이 되어버림 |
sorted(lst2) | 정렬된 새 리스트 리턴 (lst2 안 바뀜) |
lst2 = lst[:] # 얕은 복사 → ✅ 새로운 리스트
lst2.sort() # ✅ 정렬 (리턴값 사용 X)
lst2[0][0] = 999 # ✅ 내부 요소 변경
→ lst2는 여전히 리스트이고 정상 출력됨.